The goal of this Notebook is to take the parts of NovemberPhyloProc.ipynb that I expect to go into the actual manuscript.

Figure Outline

3/20/2017 Re-ran whole pipeline end-to-end. Hits on everything (including igg gp41 0 day) except only trending on gp41 Month 6.5. However I don’t see family level groups for gp41 that relate to community structure (q < 0.2, p < 0.05). I may just try running everything end to end a few more times to see what happens. This because I want to see how consistant (or otherwise) the results are between runs. Also, I’m going to start setting seeds now.

Loading libraries, functions and data

Libraries

# only use library paths in the anaconda environment

#.libPaths(grep('anaconda3', .libPaths(), value = T))
.libPaths()
[1] "/data/users/cram/Project/Nyvac_096_Microbiome/packrat/lib/x86_64-pc-linux-gnu/3.6.0"
[2] "/data/users/cram/Project/Nyvac_096_Microbiome/packrat/lib-ext"                      
[3] "/data/users/cram/Project/Nyvac_096_Microbiome/packrat/lib-R"                        
R.version
               _                                       
platform       x86_64-pc-linux-gnu                     
arch           x86_64                                  
os             linux-gnu                               
system         x86_64, linux-gnu                       
status         beta                                    
major          3                                       
minor          6.0                                     
year           2019                                    
month          04                                      
day            11                                      
svn rev        76379                                   
language       R                                       
version.string R version 3.6.0 beta (2019-04-11 r76379)
nickname       Planting of a Tree                      
sessionInfo()
R version 3.6.0 beta (2019-04-11 r76379)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.2 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] tidyselect_0.2.5    reshape2_1.4.3      purrr_0.2.5         splines_3.6.0       lattice_0.20-38    
 [6] rhdf5_2.22.0        colorspace_1.3-2    viridisLite_0.3.0   htmltools_0.3.6     stats4_3.6.0       
[11] mgcv_1.8-25         survival_2.43-1     rlang_0.4.0         pillar_1.3.0        glue_1.3.0         
[16] BiocGenerics_0.24.0 bindrcpp_0.2.2      foreach_1.4.4       bindr_0.1.1         plyr_1.8.4         
[21] stringr_1.3.1       zlibbioc_1.24.0     Biostrings_2.46.0   munsell_0.5.0       gtable_0.2.0       
[26] codetools_0.2-15    phyloseq_1.22.3     knitr_1.20          Biobase_2.38.0      permute_0.9-4      
[31] IRanges_2.12.0      biomformat_1.6.0    parallel_3.6.0      Rcpp_0.12.19        scales_1.0.0       
[36] vegan_2.5-3         S4Vectors_0.16.0    jsonlite_1.5        XVector_0.18.0      gridExtra_2.3      
[41] digest_0.6.18       ggplot2_3.1.0       packrat_0.4.8-1     stringi_1.2.4       dplyr_0.7.7        
[46] cowplot_0.9.3       grid_3.6.0          ade4_1.7-13         tools_3.6.0         magrittr_1.5       
[51] breakaway_4.6.11    lazyeval_0.2.1      tibble_1.4.2        cluster_2.0.7-1     crayon_1.3.4       
[56] ape_5.2             pkgconfig_2.0.2     MASS_7.3-51.1       Matrix_1.2-15       data.table_1.11.8  
[61] viridis_0.5.1       assertthat_0.2.0    rstudioapi_0.8      iterators_1.0.10    R6_2.3.0           
[66] multtest_2.34.0     igraph_1.2.2        nlme_3.1-137        compiler_3.6.0     
# https://stackoverflow.com/questions/46354826/have-a-function-that-calls-library-and-takes-either-a-package-or-its-name-as-inp


# Also return package version when loading in packages
# accept strings or functions
libver <- function(pac){

    pac <- as.character(substitute(pac))
    library(pac, character.only=TRUE)
    packageVersion(pac)
    }
#libver("dada2")
#libver("ggplot2")
libver("Cairo")
[1] ‘1.5.9’
# Much of the data handling
libver('phyloseq')
[1] ‘1.22.3’
# A bunch of environments, including ggplot, dplyr, tidyr, and broom, which I use a lot
libver('tidyverse')
Registered S3 method overwritten by 'rvest':
  method            from
  read_xml.response xml2
── Attaching packages ────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.1.0     ✔ purrr   0.2.5
✔ tibble  1.4.2     ✔ dplyr   0.7.7
✔ tidyr   0.8.2     ✔ stringr 1.3.1
✔ readr   1.1.1     ✔ forcats 0.3.0
── Conflicts ───────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
[1] ‘1.2.1’
# Mostly for concatenating ggplots
library(gridExtra); packageVersion("gridExtra")

Attaching package: ‘gridExtra’

The following object is masked from ‘package:dplyr’:

    combine
[1] ‘2.3’
# I use this surprisingly not a lot here.
library(vegan); packageVersion("vegan")
Loading required package: permute
Loading required package: lattice
This is vegan 2.5-3
[1] ‘2.5.3’
# For making trees
# libver('phangorn')
# A prerequesite to phangorn
# libver("DECIPHER")
# Some pre-processing stuff
# libver("dada2")
# I usually reshape with tidyverse tools now, but melt and cast are often easier in a pinch
# libver("reshape2")
# For replacing NaNs without too much thought.
# libver("imputeMissings")
# Deal with proportional data, especially useful for calculating proportionality phi
libver('compositions')
Loading required package: tensorA

Attaching package: ‘tensorA’

The following object is masked from ‘package:base’:

    norm

Loading required package: robustbase
Loading required package: energy
Loading required package: bayesm
Welcome to compositions, a package for compositional data analysis.
Find an intro with "? compositions"


Attaching package: ‘compositions’

The following objects are masked from ‘package:stats’:

    cor, cov, dist, var

The following objects are masked from ‘package:base’:

    %*%, scale, scale.default
[1] ‘1.40.2’
# Works with tidyverse to make model output tidy
libver('broom')
[1] ‘0.5.0’
# Make pretty tables
libver('knitr')
[1] ‘1.20’
libver('kableExtra')
[1] ‘0.9.0’
# Let those pretty tables actually show up in a jupyter notebook
#library('IRdisplay')
# For bootstrapping
libver('boot')

Attaching package: ‘boot’

The following object is masked from ‘package:robustbase’:

    salinity

The following object is masked from ‘package:lattice’:

    melanoma
[1] ‘1.3.20’
# Calculate kernel regressions
libver("MiRKAT")
Loading required package: survival

Attaching package: ‘survival’

The following object is masked from ‘package:boot’:

    aml

The following object is masked from ‘package:robustbase’:

    heart

Loading required package: PearsonDS
Loading required package: GUniFrac
Loading required package: ape

Attaching package: ‘ape’

The following object is masked from ‘package:compositions’:

    balance

Loading required package: matrixStats

Attaching package: ‘matrixStats’

The following objects are masked from ‘package:robustbase’:

    colMedians, rowMedians

The following object is masked from ‘package:dplyr’:

    count

Loading required package: MASS

Attaching package: ‘MASS’

The following object is masked from ‘package:dplyr’:

    select
[1] ‘1.0.1’
libver("car")
Loading required package: carData

Attaching package: ‘car’

The following object is masked from ‘package:boot’:

    logit

The following object is masked from ‘package:dplyr’:

    recode

The following object is masked from ‘package:purrr’:

    some
[1] ‘3.0.2’
#libver(mclust)
#libver(chemometrics)
libver(purrrlyr)
[1] ‘0.0.3’
libver('qvalue')
[1] ‘2.10.0’
libver("breakaway")
[1] ‘4.6.11’

Functions

I have put the functions in a library file

source('libraries/library096.R')

Data

# Set upOriginal to false, if you want to used user-reprocessed data.
# Results may differ slightly from those in the manuscript due to inter-run variation
# especially in the tree-ing algorithm.
 upOriginal <- TRUE
# upOriginal <- FALSE
# For permutation tests, how fast do things need to run
# 9999 for most runs, 99999 for publication quality ones suggested
jnperm <- 9999
# Data paths
getwd()
[1] "/data/users/cram/Project/Nyvac_096_Microbiome"
(mapping_file_path <- file.path('data', 'mapping_file_096a.csv'))
[1] "data/mapping_file_096a.csv"
(immune_file_path <- file.path('data', 'immune096b.csv'))
[1] "data/immune096b.csv"
if(upOriginal){
     seqtab_file_path <- file.path('data', 'seqtab.nochimNov2017.csv')
     taxa_file_path <- file.path('data', 'TaxaNov2017.csv')
     tree_path <- file.path('data', 'phylogeny096NovTree.tre')
    } else {
     seqtab_file_path <- file.path('data1', 'seqtab.nochimMar2018.csv')
     taxa_file_path <- file.path('data1', 'TaxaMar2018.csv')
     tree_path <- file.path('data1', 'phylogeny096Mar2018tre.tre')
}

seqtab_file_path
[1] "data/seqtab.nochimNov2017.csv"
taxa_file_path
[1] "data/TaxaNov2017.csv"
tree_path
[1] "data/phylogeny096NovTree.tre"
# Sequence data
seqtab.nochim.data <- read.csv(seqtab_file_path)

seqtabNames = gsub('\\.', '-',
    gsub('.fastq', '', seqtab.nochim.data$X)
                   )

seqtab.nochim = as.matrix(seqtab.nochim.data[,-1])
rownames(seqtab.nochim) = seqtabNames
# Taxa names
taxa.data <- read.csv(taxa_file_path)
taxa = taxa.data[,-1]

## I reverse complemented the sequences to generate the taxonomy
# (but only in this latest re-run, not the original)
## The following undoes that reverse complement to get original sequence
#rownames(taxa) = dada2:::rc(taxa.data[,1]) 

if(upOriginal){
    rownames(taxa) = (taxa.data[,1])} else {
    rownames(taxa) = dada2:::rc(taxa.data[,1]) 
}

taxa <- as.matrix(taxa)
# Mapping file
mapping.data <- read_csv(mapping_file_path) %>%
mutate(pub_id = sapply(pub_id,  function(x) {as.numeric(gsub("096-", "", x))}))
Parsed with column specification:
cols(
  SampleID = col_character(),
  BarcodeSequence = col_character(),
  LinkerPrimerSequence = col_character(),
  ReversePrimer = col_character(),
  run_prefix = col_character(),
  pub_id = col_character(),
  Sex = col_character(),
  Visit = col_integer(),
  visitRank = col_integer(),
  RXCode = col_character(),
  Description = col_character()
)
The `printer` argument is deprecated as of rlang 0.3.0.
This warning is displayed once per session.
#mapping = mapping.data[,-1]
#rownames(mapping) = mapping.data[,1]
#mapping <- as.matrix(mapping)
head(mapping.data)
# Immune Data
immune.data0 <- read_csv(immune_file_path)
Parsed with column specification:
cols(
  visitno = col_integer(),
  rx_code = col_character(),
  type = col_character(),
  antigen = col_character(),
  mag = col_double(),
  mag_bl = col_double(),
  response = col_integer(),
  day = col_integer(),
  month = col_double(),
  ct = col_character(),
  response_j = col_double(),
  assay = col_character(),
  pub_id = col_character()
)
immune.data <- mutate(immune.data0, pub_id = sapply(pub_id,  function(x) {as.numeric(gsub("096-", "", x))}))
levels(immune.data$antigen) <- gsub("[ /]", ".", levels(immune.data$antigen))
head(immune.data)
# Phylogenetic tree
seqs <- dada2::getSequences(seqtab.nochim)
names(seqs) <- seqs

pt <- ape::read.tree(file=tree_path)

pt2 <- phangorn::midpoint(pt)
immune.data$antigen %>% unique
[1] "gp41"               "p24"                "Con.6.gp120.B"      "ZM96.gp140"         "gp70_B.CaseA_V1_V2"
[6] "ANY.ENV.PTEG"      

Save options to a variable

par0 <- options()

Pre-processing

## minimal sample identification data
pub_id_key <- unique(immune.data[,c("pub_id", "rx_code", "ct")])

sample_sm0 <- dplyr::select(mapping.data, SampleID, pub_id, sex = Sex, muVisit = Visit, muVisitRank = visitRank)
sample_sm <- left_join(sample_sm0, pub_id_key, by = "pub_id") %>%
as.data.frame %>%
tibble::column_to_rownames(var = "SampleID")
`chr_along()` is deprecated as of rlang 0.2.0.
This warning is displayed once per session.Column `pub_id` has different attributes on LHS and RHS of join
# rownames(sample_sm)
# head(sample_sm)
# Make raw phyloseq object
ot <- otu_table(seqtab.nochim, taxa_are_rows=FALSE)

tt <- tax_table(taxa)
dimnames(tt) = dimnames(taxa)

spl <- sample_data(sample_sm)

psN is a really raw phyloseq object * OTU names are given as accession numbers * Numbers are in total counts * We have samples from both time points

# Quite raw phyloseq object. Species names are given as accession numbers
psN <- phyloseq(ot, tt, spl, pt2)

psN
phyloseq-class experiment-level object
otu_table()   OTU Table:         [ 960 taxa and 47 samples ]
sample_data() Sample Data:       [ 47 samples by 6 sample variables ]
tax_table()   Taxonomy Table:    [ 960 taxa by 8 taxonomic ranks ]
phy_tree()    Phylogenetic Tree: [ 960 tips and 959 internal nodes ]

I want to make a phyloseq object for use in essentally all of the subsequent analyses. Features include: * Some basic taxonomic pre-processing. * No uncharacterized phyla. * Only OTUs that show up at least 10% of the time in the final data set . * Do this after filtering samples. * No tip-glomming. I’ll save that untill later. * Immune data is included in the sample data table. * We’ll do Andrew’s representitive IgGs and IgAs. * We only have samples from visit 1. * We only have samples from experemental (not control) groups.

immune.data %>% pull(type) %>% unique
[1] "IgA"  "IgG"  "CD4+"
#immune.data %>% unite(type_antigen ,type, antigen, sep = "_")
immune.data %>% dplyr::select(pub_id, month, type, antigen, mag) %>% 
filter(month %in% c(0, 6.5, 12)) %>%
unite(type_antigen, type, antigen, sep = "_") %>%
unite(type_antigen_month,type_antigen, month, sep = "_Month_") %>%
spread(key = type_antigen_month, value = mag, drop = TRUE) -> immune.table
immune.table %>% head

Initial Taxonomic filter.

Some investegation suggested by the phyloseq tutorials to identify phyla for removal, and to identify an abundance threshold

psN
phyloseq-class experiment-level object
otu_table()   OTU Table:         [ 960 taxa and 47 samples ]
sample_data() Sample Data:       [ 47 samples by 6 sample variables ]
tax_table()   Taxonomy Table:    [ 960 taxa by 8 taxonomic ranks ]
phy_tree()    Phylogenetic Tree: [ 960 tips and 959 internal nodes ]
psN %>% subset_samples(!is.na(pub_id))
phyloseq-class experiment-level object
otu_table()   OTU Table:         [ 960 taxa and 38 samples ]
sample_data() Sample Data:       [ 38 samples by 6 sample variables ]
tax_table()   Taxonomy Table:    [ 960 taxa by 8 taxonomic ranks ]
phy_tree()    Phylogenetic Tree: [ 960 tips and 959 internal nodes ]
# skip the blanks
psN %>% subset_samples(!is.na(pub_id)) %>%
# OTUs must be characterized to phylum
subset_taxa(!is.na(Phylum)& !Phylum %in% c("", "uncharacterized")) -> psN_hasPhylum
psN_hasPhylum
phyloseq-class experiment-level object
otu_table()   OTU Table:         [ 929 taxa and 38 samples ]
sample_data() Sample Data:       [ 38 samples by 6 sample variables ]
tax_table()   Taxonomy Table:    [ 929 taxa by 8 taxonomic ranks ]
phy_tree()    Phylogenetic Tree: [ 929 tips and 928 internal nodes ]
# from 960 to 929 otus

Identifying and removing phyla with very few taxa in them

prevdf = apply(X = otu_table(psN_hasPhylum),
                 MARGIN = ifelse(taxa_are_rows(psN_hasPhylum), yes = 1, no = 2),
                 FUN = function(x){sum(x > 0)})
# Add taxonomy and total read counts to this data.frame
prevdf = data.frame(Prevalence = prevdf,
                      TotalAbundance = taxa_sums(psN_hasPhylum),
                      tax_table(psN_hasPhylum))

plyr::ddply(prevdf, "Phylum", function(df1){cbind(mean(df1$Prevalence),sum(df1$Prevalence))})
filterPhyla = c("Verrucomicrobia", "Tenericutes", "Elusimicrobia", "Synergistetes")
psN_MainPhyla = subset_taxa(psN_hasPhylum, !Phylum %in% filterPhyla)
psN_MainPhyla
phyloseq-class experiment-level object
otu_table()   OTU Table:         [ 922 taxa and 38 samples ]
sample_data() Sample Data:       [ 38 samples by 6 sample variables ]
tax_table()   Taxonomy Table:    [ 922 taxa by 8 taxonomic ranks ]
phy_tree()    Phylogenetic Tree: [ 922 tips and 921 internal nodes ]
# Determining abundance threshold
prevdf1 = subset(prevdf, Phylum %in% get_taxa_unique(psN_MainPhyla, "Phylum"))
ggplot(prevdf1, aes(TotalAbundance, Prevalence / nsamples(psN_hasPhylum),color=Phylum)) +
  # Include a guess for parameter
  geom_hline(yintercept = 0.05, alpha = 0.5, linetype = 2) + geom_point(size = 2, alpha = 0.7) +
  scale_x_log10() +  xlab("Total Abundance") + ylab("Prevalence [Frac. Samples]") +
  facet_wrap(~Phylum) + theme(legend.position="none")

# Determining abundance threshold
prevdf1 = subset(prevdf, Phylum %in% get_taxa_unique(psN_MainPhyla, "Phylum"))
ggplot(prevdf1, aes(TotalAbundance, Prevalence / nsamples(psN_hasPhylum),color=Phylum)) +
  # Include a guess for parameter
  geom_hline(yintercept = 0.05, alpha = 0.5, linetype = 2) + geom_point(size = 2, alpha = 0.7) +
  scale_x_continuous() +  xlab("Total Abundance") + ylab("Prevalence [Frac. Samples]") +
  facet_wrap(~Phylum) + theme(legend.position="none")

Are differences between participants greater than differences within participants accross time-points?

Make a phyloseq object like psN2 but with all participants, psN2A Code copied from above.

Constructing psN2

(phyloseq object of relative abundances)

And psN1 (phyloseq object of counts)

psN1A and psN2A include all participants, and will be used to look at variability within participants

psN %>%
# add all the immune data
phylo_join(immune.table, by = "pub_id") %>%
# only use data from humans (no extraction controls)
subset_samples(is.finite(muVisitRank)) %>%
# only otus from known taxa that show up frequently enough
subset_taxa(!is.na(Phylum)& !Phylum %in% c("", "uncharacterized")) %>%
subset_taxa(!Phylum %in% filterPhyla) %>%
# only otus that show up in at least 10% of samples
prevalence_filter_taxa %>%
# convert to relative abundance

tag_phyloseq%>%
# Instead of naming each taxon with its full sequence, we use the "tag" instead
swap.phyloseq.taxnames %>%
pass -> psN1A # Save pre relative abundance transformation
`list_len()` is deprecated as of rlang 0.2.0.
Please use `new_list()` instead.
This warning is displayed once per session.Setting row names on a tibble is deprecated.
# add is-male
manColumn <- psN1A %>% sample_data %>% as('data.frame') %>% rownames_to_column  %>% mutate(isMale = testIsMaleVec(sex)) %>% dplyr::select(rowname, isMale)
psN1A <- phylo_join(psN1A, manColumn, by = 'rowname')

## psN2 is like psN1 but with relative abundances
psN1A %>%
transform_sample_counts(function(x) {x/sum(x)}) %>%
# The "tag" is a new name that takes into account the rest of the taxonomy data
# the tag may need to be updated after any agglomeration
pass-> psN2A
# filter to just microbiome visit 1 and experemental treatments
psN1A %>%
subset_samples(muVisitRank == 1) %>%
subset_samples(ct == "T") %>%
pass -> psN1

psN2A %>%
subset_samples(muVisitRank == 1) %>%
subset_samples(ct == "T") %>%
pass -> psN2
# Calculate weighted unifrac distances and role those in.
psN2.wuf <- phyloseq::distance(psN2, method = "wunifrac")
psN2.pcoa <- capscale(psN2.wuf ~ 1)
psN2.pcoa.df <- psN2.pcoa %>% scores(display = "sites") %>%
        as.data.frame %>% 
        rownames_to_column %>% 
        dplyr::select('rowname', 'MDS1', 'MDS2') %>%
        mutate(rMDS1 = rank(MDS1)) %>% # rank order of MDS1
        mutate(rrMDS1 = formatC(format = "d", rMDS1, flag = "0", width=ceiling(log10(max(rMDS1))))) %>%
        unite(newname, rrMDS1, rowname, sep = "_", remove = FALSE) %>%
        dplyr::select(-rrMDS1)

psN2 %>%
phylo_join(
    psN2.pcoa.df,
    by = 'rowname'
) -> psN2

## Even if the data are counts, 
## the weighted unifrac pcoa is still done on the relative abundances
psN1 %>%
phylo_join(
    psN2.pcoa.df,
    by = 'rowname'
) -> psN1

psN2A
phyloseq-class experiment-level object
otu_table()   OTU Table:         [ 651 taxa and 38 samples ]
sample_data() Sample Data:       [ 38 samples by 31 sample variables ]
tax_table()   Taxonomy Table:    [ 651 taxa by 10 taxonomic ranks ]
phy_tree()    Phylogenetic Tree: [ 651 tips and 650 internal nodes ]
psN1A
phyloseq-class experiment-level object
otu_table()   OTU Table:         [ 651 taxa and 38 samples ]
sample_data() Sample Data:       [ 38 samples by 31 sample variables ]
tax_table()   Taxonomy Table:    [ 651 taxa by 10 taxonomic ranks ]
phy_tree()    Phylogenetic Tree: [ 651 tips and 650 internal nodes ]
psN2
phyloseq-class experiment-level object
otu_table()   OTU Table:         [ 651 taxa and 21 samples ]
sample_data() Sample Data:       [ 21 samples by 35 sample variables ]
tax_table()   Taxonomy Table:    [ 651 taxa by 10 taxonomic ranks ]
phy_tree()    Phylogenetic Tree: [ 651 tips and 650 internal nodes ]
psN1
phyloseq-class experiment-level object
otu_table()   OTU Table:         [ 651 taxa and 21 samples ]
sample_data() Sample Data:       [ 21 samples by 35 sample variables ]
tax_table()   Taxonomy Table:    [ 651 taxa by 10 taxonomic ranks ]
phy_tree()    Phylogenetic Tree: [ 651 tips and 650 internal nodes ]

Data Curation Post mortum

How many taxa were still present after each filtering step?

# Find number of taxa in available samples
psN %>%
# add all the immune data
phylo_join(immune.table, by = "pub_id") %>%
# filter to just microbiome visit 1 and experemental treatments
subset_samples(muVisitRank == 1) %>%
subset_samples(ct == "T") %>%
prevalence_filter_taxa(thresh = 0) %>%
pass-> psInSamples
(NInSamples <- dim(otu_table(psInSamples))[2])
[1] 960
# Number of taxa with unidentified phyla
psInSamples %>%
subset_taxa(!is.na(Phylum)& !Phylum %in% c("", "uncharacterized")) %>%
pass -> psIdentifiedPhylum
(NUnkPhylum <- NInSamples - dim(otu_table(psIdentifiedPhylum))[2])
[1] 31
filterPhyla
[1] "Verrucomicrobia" "Tenericutes"     "Elusimicrobia"   "Synergistetes"  
# Phyla removed because they are in filterPhyla 
# -- each of which show up fewer than 20 times in the data set
psIdentifiedPhylum %>%
subset_taxa(!Phylum %in% filterPhyla) %>%
pass -> psNotPhylaFiltered
(NFiltPhyla <- dim(otu_table(psIdentifiedPhylum))[2] - dim(otu_table(psNotPhylaFiltered))[2])
[1] 7
# Taxa removed because there were in fewer than 10% of the samples
psNotPhylaFiltered %>%
prevalence_filter_taxa %>%
pass -> psPFT
dim(otu_table(psNotPhylaFiltered))[2] - dim(otu_table(psPFT))[2]
[1] 386

Immune figure

How to participants’ immune profiles change over time?

# When were participants vaccinated?
# Copied from protocol apendix E
# visitno 1 is a screening visit, I assign it NaN
dayTable = data.frame(
    visitno = seq(from = 1, to = 14, by = 1),
    day = c(NaN, 0, 14, 28, 42, 84, 98, 168, 182, 196, 273, 364, 455, 545),
    month = c(NaN, 0, 0.5, 1, 1.5, 3, 3.5, 6, 6.5, 7, 9, 12, 15, 18)
)
vac <- data.frame(
    visitno = c(2, 4, 6, 8)
    )
vac <- left_join(vac, dayTable, by = 'visitno')

vac
# Representitive antigens for further considerations
# These are essentially zero (mag = 1) at baseline
ants1 <- c('Con.6.gp120.B', 'ZM96.gp140', 'gp70_B.CaseA_V1_V2')
# These have measurable baseline magnitudes
ants2 <- c('gp41', 'p24')
donor.immune <-  psN2 %>% sample_data %>% as('data.frame') %>% dplyr::select(pub_id) %>%
left_join(immune.data, by = 'pub_id')
Column `pub_id` has different attributes on LHS and RHS of join
donor.immune %>% head
psN %>% sample_data %>%
as('data.frame') %>% 
filter(!is.na(pub_id)) %>%
pull(pub_id) %>%
unique %>%
pass -> microbiomeCohort
immune.data %>% filter(pub_id %in% microbiomeCohort) %>%
pass -> donor.immune

donor.immune %>% head
options(par0)
iggplot <- immune.data %>%
mutate(inCohort = pub_id %in% microbiomeCohort) %>%
filter(type == 'IgG', antigen %in% c(ants1, ants2)) %>%
mutate(antigen = factor(antigen, levels = c(ants2, ants1))) %>% # reorder facets
ggplot(aes(x = month, y =mag, group = pub_id, colour = inCohort, alpha = inCohort)) +
geom_line() +
geom_point() +
geom_rug(data = vac, aes(x = month), inherit.aes = F, color = 'blue') +
facet_grid(antigen ~ rx_code, labeller = label_wrap_gen()) +
theme_bw() +
theme(strip.text.y = element_text(angle = 0),
      axis.text.x = element_text(angle = 90, hjust = 1)) +
scale_y_log10(breaks = 10^(0:5)) +
scale_colour_manual(values = c("black", "red")) +
scale_alpha_manual(values = c(.6, 1)) + 
labs(y = "BAMA Response Magnitude")



ggsave('figures/useiggsAllParticipants.svg')
Saving 7 x 7 in image
# To fix. Control groups don't show up in this version.
iggplot

iggplot <- immune.data %>%
mutate(inCohort = pub_id %in% microbiomeCohort) %>%
filter(type %in% c('IgA', 'CD4+') & antigen %in% c(ants2, 'ANY.ENV.PTEG'))%>%
mutate(antigen = factor(antigen, levels = c(ants2, 'ANY.ENV.PTEG'))) %>% # reorder facets
ggplot(aes(x = month, y =mag, group = pub_id, colour = inCohort, alpha = inCohort)) +
geom_line() +
geom_point() +
geom_rug(data = vac, aes(x = month), inherit.aes = F, color = 'blue') +
facet_grid(antigen ~ rx_code, labeller = label_wrap_gen(), scales = 'free_y') +
theme_bw() +
theme(strip.text.y = element_text(angle = 0),
      axis.text.x = element_text(angle = 90, hjust = 1)) +
scale_y_log10(breaks = 10^c(
    seq(from = -2, to = 0, by = 0.25), seq(from = 0, to = 5, by = 1)
), labels = function(x) round(as.numeric(x), digits=3)) +
scale_colour_manual(values = c("black", "red")) +
scale_alpha_manual(values = c(.6, 1)) +
labs(y = "BAMA Response Magnitude")

iggplot

ggsave('figures/useIgACD4AllParticipants.svg')
Saving 7.29 x 4.5 in image

# To fix. Control groups don't show up in this version.
iggplot <- donor.immune %>% filter(type == 'IgG', antigen %in% c(ants1, ants2)) %>%
mutate(antigen = factor(antigen, levels = c(ants2, ants1))) %>% # reorder facets
ggplot(aes(x = month, y =mag, group = pub_id)) + geom_point(alpha = 0.6) + geom_line(alpha = 0.4) +
geom_rug(data = vac, aes(x = month), inherit.aes = F, color = 'blue') +
facet_grid(antigen ~ rx_code, labeller = label_wrap_gen()) +
theme_bw() + theme(strip.text.y = element_text(angle = 0), axis.text.x = element_text(angle = 90, hjust = 1)) +
scale_y_log10(breaks = 10^(0:5)) +
labs(y = "BAMA Response Magnitude")
iggplot
ggsave('figures/useiggs.svg') # I can no longer save pngs with transparency, going to svg
Saving 7.29 x 4.5 in image

# To fix. Control groups don't show up in this version.

Magnitude and variance of vaccine response per group

colnames(immune.data)
 [1] "visitno"    "rx_code"    "type"       "antigen"    "mag"        "mag_bl"     "response"   "day"        "month"     
[10] "ct"         "response_j" "assay"      "pub_id"    

Plan. Calculate mean and variance of each antigen-type at visit 9, and at baseline.

#
geomean <- function(x, na.rm = FALSE, trim = 0, ...)
{
exp(mean(log(x, ...), na.rm = na.rm, trim = trim, ...))
}
 
geosd <- function(x, na.rm = FALSE, ...)
{
exp(sd(log(x, ...), na.rm = na.rm, ...))
}
immune.data %>% dplyr::filter(visitno %in% c(9), ct == "T") %>% dplyr::select(pub_id, antigen, visitno, mag, mag_bl, type) %>% group_by(antigen, type) %>%
summarize(mean_mag = geomean(mag), mean_bl = geomean(mag_bl), sd_mag = geosd(mag), sd_bl = geosd(mag_bl)) %>%
mutate(var_over_mean_mag = sd_mag^2/mean_mag, var_over_mean_bl = sd_bl^2/mean_bl)

Mean variance and variance over mean of each value.

immune.data %>% dplyr::filter(visitno %in% c(9), ct == "T") %>% dplyr::select(pub_id,rx_code, antigen, visitno, mag, mag_bl, type) %>% group_by(rx_code, antigen, type) %>%
summarize(mean_mag = geomean(mag), mean_bl = geomean(mag_bl), sd_mag = geosd(mag), sd_bl = geosd(mag_bl)) %>%
mutate(var_over_mean_mag = sd_mag^2/mean_mag, var_over_mean_bl = sd_bl^2/mean_bl) %>% 
gather(key = "meas", value = "value", mean_mag, mean_bl, sd_mag, sd_bl, var_over_mean_mag, var_over_mean_bl) %>% 
group_by(antigen, type, meas) %>% summarize(mean_val = mean(value)) %>%
spread(key = "meas", value = "mean_val")

As above, but this time, calculated seperately for each treatment group and then those values averaged. ZM96 and gp70 surprisingly large variance over mean. Maybe should look at delta magnitude

immune.data%>% head
immune.data %>% 
mutate(mag_delta = mag / mag_bl) %>%
mutate(mag_delta = if_else(mag_delta < 1, 1, mag_delta)) %>%
dplyr::filter(visitno %in% c(9), ct == "T") %>% dplyr::select(pub_id,rx_code, antigen, visitno, mag, mag_bl, mag_delta, type) %>% group_by(rx_code, antigen, type) %>%
summarize(mean_mag = geomean(mag), mean_bl = geomean(mag_bl), mean_delta = geomean(mag_delta), sd_mag = geosd(mag), sd_bl = geosd(mag_bl), sd_delta = geosd(mag_delta)) %>%
mutate(var_over_mean_mag = sd_mag^2/mean_mag, var_over_mean_bl = sd_bl^2/mean_bl, var_over_mean_delta = sd_delta^2/mean_delta) %>% 
gather(key = "meas", value = "value", mean_mag, mean_bl, sd_mag, sd_bl, var_over_mean_mag, var_over_mean_bl, mean_delta, sd_delta, var_over_mean_delta) %>% 
group_by(antigen, type, meas) %>% summarize(mean_val = mean(value)) %>%
spread(key = "meas", value = "mean_val") %>%
arrange(type)

Number of participants per group

All participants

immune.data %>% 
group_by(rx_code) %>%
summarize(Unique_ids = n_distinct(pub_id))

Participants with microbiome data

donor.immune %>% 
group_by(rx_code) %>%
summarize(Unique_ids = n_distinct(pub_id))

Further breakdown of participants providing microbiota per group

Number of participants with a given day of first sample.

psN2 %>% sample_data %>% data.frame %>% group_by(muVisit) %>% summarize(n = length(muVisit))

How many donors were there from each treatment?

psN2 %>% sample_data %>% data.frame %>% group_by(rx_code) %>% summarize(n = length(muVisit))

Breakdown by both visit and treatment

sampleBreakdown <- psN2 %>% sample_data %>% data.frame %>% group_by(muVisit, rx_code) %>% summarize(n = length(muVisit)) %>% spread(key = rx_code, value = n, fill = 0) %>% mutate(total = T1 + T2 + T3 + T4)

sbcs <- colSums(sampleBreakdown)

sampleBreakdown <- bind_rows(sampleBreakdown, sbcs)

sampleBreakdown %>%
kable("html", escape = F, digits = 3, align = 'c') %>%
#kable_styling("striped", "hover", full_width = F) %>%
#collapse_rows(columns = 1:2, latex_hline = "full") %>%
pass-> sampleBreakdown.html
sampleBreakdown.html
muVisit T1 T2 T3 T4 total
2 3 0 0 0 3
9 4 2 5 0 11
12 0 1 1 5 7
23 7 3 6 5 21

sampleBreakdown.html %>% cat(file = 'tables/sampleBreakdown.html')

Weighted Unifrac

Variability within vs between participants

psN2A.wuf <- phyloseq::distance(psN2A, method = "wunifrac")
psN2A %>% sample_data %>% .[1:5, 1:10]
Sample Data:        [5 samples by 10 sample variables]:
psN2A %>% sample_data %>% as("data.frame") %>% rownames_to_column(var = "Sample") %>% dplyr::select(Sample, pub_id) %>%
pass -> S2P
All.equal <- Vectorize(function(x,y){x == y})
## Convert distance matrix into long form matrix
psN2A.wuf %>% as.matrix %>% as.data.frame %>%
rownames_to_column(var = "SampleX")%>%
gather(key = "SampleY", value = "WufDist", -SampleX) %>%
left_join(S2P, by = c("SampleX" = "Sample")) %>% rename(pub_id_x = pub_id) %>%
left_join(S2P, by = c("SampleY" = "Sample")) %>% rename(pub_id_y = pub_id) %>%
mutate(SampleX = as.numeric(str_extract(SampleX, "[0-9][0-9]"))) %>%
mutate(SampleY = as.numeric(str_extract(SampleY, "[0-9][0-9]"))) %>%
## discard diagonal discard and upper half of triangular matrix
filter(SampleX < SampleY) %>%
mutate(isSamePerson = All.equal(pub_id_x, pub_id_y)) %>%
# ## discard cases where pub_id is unknown
# filter(is.finite(pub_id_x) & is.finite(pub_id_y))
pass -> AllWufDist

AllWufDist %>% head
#AllWufDist %>% ggplot(aes(x = isSamePerson, y = WufDist)) + geom_violin() + geom_dotplot(binaxis = "y", stackdir = "center", binwidth = .005)

Get Mean values for between participant and within participant weighted unifrac distances.

WufMeans <- AllWufDist %>% group_by(isSamePerson) %>% summarize(mean = mean(WufDist))
WufMeans
#SameAndDiff <- data.frame(comparison = c("different", "same"), WufMeans$mean)
SameAndDiff <- WufMeans %>% spread(key = isSamePerson, value = mean) %>% rename(between = 'FALSE', within = 'TRUE') %>% mutate(diff = between-within)
SameAndDiff
#SameAndDiff[1,2] - SameAndDiff[2,2] # Difference in weighted unifrac dissimilarity between same and different partcipants

Bootstrapped confidence intervals

Bootstrap some confidence intervals of within and between participant weighted unifrac distances.

# Split the data
SamePersonWufDist <- AllWufDist %>% filter(isSamePerson) #%>% dplyr::select(WufDist)
DifferentPersonWufDist <- AllWufDist %>% filter(!isSamePerson)
set.seed(334)

bootsSame <- rsample::bootstraps(SamePersonWufDist, times = 10000)
bootsDifferent <- rsample::bootstraps(DifferentPersonWufDist, times = 10000)
mean_of_bootstrap <- function(split){
    locVals <- rsample::analysis(split)$WufDist
    mean(locVals)
}
boot_meansSame <- bootsSame %>% mutate(mean = map_dbl(splits, mean_of_bootstrap)) %>% dplyr::select(mean)

boot_meansDifferent <- bootsDifferent %>% mutate(mean = map_dbl(splits, mean_of_bootstrap)) %>% dplyr::select(mean)

boot_means <- bind_cols(within = boot_meansSame$mean, between = boot_meansDifferent$mean) %>%
mutate(isDifferentBigger = between>within, 
      DifMinusSame = between - within)
#boot_means

1- sum(boot_means$isDifferentBigger)/length(boot_means$isDifferentBigger)
[1] 0.012

The above is a bootstrapped P value that the two are different from eachother. Per a conversation I had with Klaus Hubert. Still need to find a justification that this approach is legit.

boot_means %>% ggplot(aes(x = DifMinusSame)) + geom_histogram()

A histogram of bootstrapped differences between within participant and between participant mean values.

quantile(boot_means$DifMinusSame, c(0.025, 0.5, 0.975))
      2.5%        50%      97.5% 
0.01273644 0.08721308 0.15521066 

95% confidence intervals of the differences between same and different person microbiota.

Permutation based P values

PermWufDist <- AllWufDist %>% modelr::permute(10000, WufDist)
mean_of_is_same <- function(df){df %>% as.data.frame %>% group_by(isSamePerson) %>% summarize(mean(WufDist)) %>% spread(isSamePerson, `mean(WufDist)`)}
test <- PermWufDist %>% pull(perm) %>% .[[1]] %>% as.data.frame
test %>% head
mean_of_is_same(test)
SameAndDiff
permutedMeans <- PermWufDist %>% mutate(meanvals = map(perm, mean_of_is_same)) %>% dplyr::select(meanvals) %>% unnest %>% 
rename(between = `FALSE`, within = `TRUE`) %>%
mutate(diff = between - within, absdif = abs(diff)) %>%
mutate(isExtreme = absdif >= SameAndDiff$diff, isExtreme1Tail = diff >= SameAndDiff$diff)
#permutedMeans %>% ggplot(aes(x = diff)) + geom_histogram() + geom_vline(xintercept = SameAndDiff$diff)
permutedMeans %>% ggplot(aes(x = diff)) + geom_histogram() + geom_vline(xintercept = SameAndDiff$diff)

Fraction of permuted values less extreme than difference between same and different.

#permutedMeans %>% mutate(isExtreme = absdif >= SameAndDiff$diff)
sum(permutedMeans$isExtreme) / length(permutedMeans$isExtreme)
[1] 0.0153
sum(permutedMeans$isExtreme1Tail) / length(permutedMeans$isExtreme1Tail)
[1] 0.0073

Two and one tailed permuted p-values

Plot of confedence interval and raw data

options(repr.plot.width=6, repr.plot.height= 6)
boot_means  %>% dplyr::select(within, between) %>% gather(key = "comparison", value = "WufDist") %>% ggplot(aes(x = comparison, y = WufDist)) + 
geom_dotplot(data = AllWufDist %>% mutate(isSamePerson2 = if_else(isSamePerson, "within", "between"))
             , aes(x = isSamePerson2, y = WufDist), binaxis = "y", stackdir = "center", binwidth = .01, colour = "gray40", fill = "white") + 
geom_violin(fill = NA) + 
geom_point(data = data.frame(comparison = c("within", "between"), WufDist = WufMeans$mean), aes(x = comparison, y = c(WufDist[2], WufDist[1])), shape = 22, fill = "black", size = 2)+
theme_bw() +
scale_x_discrete(limits = c("within", "between")) + labs(y = "Weighted Unifrac Distance") #
ggsave('figures/BetweenVsWithin.png')  
Saving 7.29 x 4.5 in image

Figure: Open circles represent weighed unifrac distances associated with pairs of samples taken from the same set of participants, at different time points (“within”), and samples taken from different sets of participants (“between”). Black squares indicate the observed mean of the within and between values. Violins indicate distributions of bootstrapped mean values.

Variability at earliest sampling

psN2.wuf <- phyloseq::distance(psN2, method = "wunifrac")
psN2.pcoa <- capscale(psN2.wuf ~ 1)
# How much variance si explained by each weighted unifrac axis
# Note, ten axes cover 95% of the variance. 
# I'm not going to look beyond that for any test.
data.frame(eig = psN2.pcoa$CA$eig) %>%
rownames_to_column('axis') %>%
mutate(proportion = eig/sum(eig)) %>%
mutate(cumulative = cumsum(proportion))
my_breaks = c(1, 75, 250, 500, 1000,2000)
psN2 %>% mutate_phyloseq_sample(
                               mc41 = factor(medcode_hl(IgG_gp41_Month_0)),
                                                log120 = (IgG_Con.6.gp120.B_Month_12)) -> psN2_mod
psN2_mod%>%
sample_data() %>%
ggplot(aes(x = MDS1, y = MDS2)) + geom_point(aes(fill = mc41), size = 5, stroke = 1, shape = 21) +
coord_fixed(sqrt(psN2.pcoa$CA$eig[2]/psN2.pcoa$CA$eig[1])) +
viridis::scale_fill_viridis(name = 'gp41 Baseline', direction = -1, discrete = TRUE) +
#scale_colour_manual(name = 'gp41 Primary', values = c('black', 'grey70')) + 
theme_bw() -> wuford_gp41


psN2_mod %>%
sample_data() %>%
ggplot(aes(x = MDS1, y = MDS2)) + geom_point(aes(fill = log120), size = 5, stroke = 1, shape = 21) +
coord_fixed(sqrt(psN2.pcoa$CA$eig[2]/psN2.pcoa$CA$eig[1])) +
viridis::scale_fill_viridis(name = 'gp120 Final', direction = 1, trans = "sqrt",
                           breaks = my_breaks, labels = my_breaks) +
#scale_colour_manual(name = 'gp41 Primary', values = c('black', 'grey70')) + 
theme_bw() -> wuford_gp120

par <- options()
options(repr.plot.width=11, repr.plot.height= 4)
#g <- grid.arrange(wuford_gp41, wuford_gp120, ncol = 2)
g <- cowplot::plot_grid(wuford_gp41, wuford_gp120, ncol = 2, labels = c("A", "B"), label_size = 24)
g
#ggsave('figures/wunifrac_Agp41_Bgp120_pcoa.png', g, width = 8, height = 4)
#ggsave('figures/wunifrac_Agp41_Bgp120_pcoa.svg', g, width = 8, height = 4)
cowplot::save_plot('figures/wunifrac_Agp41_Bgp120_pcoa.png', g, base_width = 8, base_height = 4)
cowplot::save_plot('figures/wunifrac_Agp41_Bgp120_pcoa.svg', g, base_width = 8, base_height = 4)

Kernel Regression and Weighted Unifrac GLM

wufKN2 <- D2K(as.matrix(psN2.wuf))
muDoners <- unique(sample_data(psN2)$pub_id)
immune.data %>%
filter(pub_id %in% muDoners) %>%
filter(
    (type == 'IgG' & 
    antigen %in% ants1 &
    month %in% c(6.5,12)
    ) |
    (type %in% c('IgG', 'IgA') &
     antigen %in% ants2 &
     month %in% c(0,6.5,12)
    ) |
    type == 'CD4+' &
    antigen == 'ANY.ENV.PTEG' &
    month %in% c(6.5, 12)
      )-> use.immune
head(use.immune)
# Do permanova and related tests to a variable of interest
# This function is pretty specific to this analysis, so I'm going to leave it
# here in the notebook file
CapVar <- function(x, nperm = 9999, transformation = medcode2, family = 'binomial'){
    ## Pull out the needed data
    
    psN2.wMDS <- psN2 %>% phylo_join(scores(psN2.pcoa, display = "sites", choices = 1:10) %>%
                    as.data.frame %>% rownames_to_column, by = 'rowname')
    
    medWuf <- NA
    rankWuf <- NA
    locPS <- phylo_join(psN2.wMDS, x, by = 'pub_id') 
    ydata0 <- sample_data(locPS)$mag
    yna <- is.na(ydata0)
    #loc.wuf <- wufKN2
    #loc.jsd <- jsdKN2
    ydata <- ydata0
    
    ydata <- ydata0[!yna]
    loc.wuf2 <- psN2.wuf %>% as.matrix %>% .[!yna, !yna]
    
    medWuf <- adonis(loc.wuf2 ~ transformation(ydata), permutations = nperm)
    #medWuf$aov.tab[1,c('R2', 'Pr(>F)')]
    
    ## Capscale returns the same results as adonis (permanova), but also gives some other interesting results
    
    medWufCap <- capscale(loc.wuf2 ~ transformation(ydata))
    capanova <- anova(medWufCap, permutations = nperm)
    
    samDf <- locPS %>% sample_data %>% as('data.frame') %>% rownames_to_column %>%
     left_join(
        vegan::scores(medWufCap, display = 'sites') %>% as.data.frame %>% dplyr::select(CAP1) %>%
        rownames_to_column, by = 'rowname') %>% .[!yna,]
    
#     # Is giving only positive results with CAP1, not sure why
#     glmAnova <- glm(medcode(ydata) ~  MDS1 + CAP1, data = samDf, family = 'binomial') %>% anova(test = "Chisq")
    loc_glm <- glm(transformation(ydata) ~  MDS1, data = samDf, family = family)
    glmAnova <- loc_glm %>% anova(test = "Chisq")
    #glmAnova['CAP1', 'Deviance']/out_capanova['NULL', 'Resid. Dev']
    
    ## check against mirkat
    loc.Kwuf2 <- wufKN2[!yna, !yna]
    mirkatP <- MiRKAT(y = transformation(ydata), Ks = loc.Kwuf2, out_type = "C", method = 'permutation', nperm = nperm)
    
    #list(medWuf, capanova, mirkatP)
    
    pred_pct <- predict(loc_glm, type = "response")
    pred_01 <- as.numeric(predict(loc_glm, type = "response") > 0.5)
    
    accuracy <- mean(transformation(ydata) == pred_01)
    
        null_glm <- update(loc_glm, ~1)

    # Canonical caluclation of McFadden's R2 for the GLM
    McFadden = 1- (logLik(loc_glm)/ logLik(null_glm))
    L.full = logLik(loc_glm)
    D.full = -2 * L.full
    L.base = logLik(null_glm)
    D.base = -2 * L.base
    n = dim(samDf)[1]
    Nagelkerke = (1 - exp((D.full - D.base)/n))/(1 - exp(-D.base/n))
    
    
    # A GLM of all weighted unifrac components
    
    
    data.frame(
        caps.P = capanova['Model', 'Pr(>F)'],
        adonisP = medWuf$aov.tab[1, 'Pr(>F)'],
        mir.P = mirkatP,
        caps.F = capanova['Model', 'F'],
        caps.R2 = medWufCap$CCA$tot.chi/medWufCap$tot.chi, 
        wuf1.P = glmAnova['MDS1', 'Pr(>Chi)'],
        wuf1.DR = glmAnova['MDS1', 'Deviance'] / glmAnova['NULL', 'Resid. Dev'],
        wuf1.McFadden = Nagelkerke,
        accuracy,
        wuf1.coef = coef(loc_glm)[2]
        #cap1.P = glmAnova['CAP1', 'Pr(>Chi)'],
        #cap1.R2 = glmAnova['CAP1', 'Deviance'] / glmAnova['NULL', 'Resid. Dev']
    )
    }
    
use.immune %>%
filter(type == 'IgG' & antigen == 'gp41'& month == 0 & ct == 'T') -> test.immune1
# Just confirming that the function works before it goes in a giant loop. I'd delete this,
# but i'll just end up needing it again if I do.
ptm = proc.time()
tps <- CapVar(test.immune1, nperm = 9999, transformation = medcode, family = 'binomial')
proc.time() - ptm
   user  system elapsed 
  0.678   0.048   0.726 
tps
use.immune %>%
filter(type == 'CD4+' & month == 6.5 & ct == 'T') -> test.immune.pteg
# Just confirming that the function works before it goes in a giant loop. I'd delete this,
# but i'll just end up needing it again if I do.
ptm = proc.time()
tps <- CapVar(test.immune.pteg, nperm = jnperm, transformation = medcode, family = 'binomial')
proc.time() - ptm
   user  system elapsed 
  0.610   0.000   0.609 
tps
# Run above function against every relevant variable.
ptm <- proc.time()

use.immune %>% 
filter(ct == 'T') %>%
group_by(type, antigen, month) %>%
do(data.frame(CapVar(., nperm = jnperm))) -> permKernTable
`env_bind_fns()` is deprecated as of rlang 0.3.0.
Please use `env_bind_active()` instead.
This warning is displayed once per session.`new_overscope()` is deprecated as of rlang 0.2.0.
Please use `new_data_mask()` instead.
This warning is displayed once per session.`overscope_eval_next()` is deprecated as of rlang 0.2.0.
Please use `eval_tidy()` with a data mask instead.
This warning is displayed once per session.

|===================                                                                              | 20% ~10 s remaining    
|========================                                                                         | 25% ~9 s remaining     
|=============================                                                                    | 30% ~8 s remaining     
|=================================                                                                | 35% ~8 s remaining     
|======================================                                                           | 40% ~7 s remaining     
|===========================================                                                      | 45% ~7 s remaining     
|================================================                                                 | 50% ~6 s remaining     
|=====================================================                                            | 55% ~5 s remaining     
|==========================================================                                       | 60% ~5 s remaining     
|===============================================================                                  | 65% ~4 s remaining     
|===================================================================                              | 70% ~4 s remaining     
|========================================================================                         | 75% ~3 s remaining     
|=============================================================================                    | 80% ~2 s remaining     
|==================================================================================               | 85% ~2 s remaining     
|=======================================================================================          | 90% ~1 s remaining     
|============================================================================================     | 95% ~1 s remaining     
|=================================================================================================|100% ~0 s remaining     
`overscope_clean()` is deprecated as of rlang 0.2.0.
This warning is displayed once per session.
permKernTable

proc.time() - ptm
   user  system elapsed 
 12.319   0.052  12.331 

The above function runs several extra tests. Results as follows:

type antigen visitno - things we run over

caps.P - Capscale test asks whether if we rotate things a bit and then try to use the best axis to compare to the data. Its similar to the wuf1.P value, but with some rotation

adonisP - p-value for a permanova test. Similar to mirkat p-value. One key exception is that igg_gp41_Month_0 falls on different sides of the 0.05 threshold.

mir.P is the p value for the kernel regression test, as run in the MiRKAT package. (Zhao et al., 2015)

caps.F and caps R2 are the f and r squared values for the capscale test.

wuf.P - is the p value of a glm comparing weighted unifrac component one against variables of interest. This test appears to always be statistically significantly positive when the mirkat test is positve.

wuf1.DR - one way of calculating an R2 value from a glm. We devide the deviance by the residual deviance

wuf1.McFadden - is a McFadden’s pseudo R^2. This turns out to be identical to the previous calculation.

accuracy - the fraction of the time that the glm predicts something falls above or below the median correctly. This turns out to not be super informative. Everything has around a 60% accuracy.

wuf1.coef - the coefficient of the glm model. The sign is relevant. Things with postive sign are associated with high values of weighted unifrac axis 1.

# Clean up so we just see the results of the kernel regression 
concisePermKernTable <- permKernTable %>% ungroup %>%
mutate(Kernel_Q = p2q(mir.P), MDS1_Q = p2q(wuf1.P)) %>%
dplyr::select(Type = type, Antigen = antigen,Month = month, Kernel_P = mir.P, Kernel_Q,
              MDS1_P = wuf1.P, MDS1_Q, GlmMDS1_R2 = wuf1.McFadden, MDS1_Coef = wuf1.coef) %>%
as.data.frame %>% 
pass -> concisePermKernTable
write.csv(format(concisePermKernTable, digits = 3), 'tables/concisePermkernTable.csv')

Table 1

# export conditionally formatted table as html

colNames1 = c(' ' = 3, 'Kernel' = 2, 'MDS' = 4)
colNames2 = c('Type', 'Antigen', 'Month', 'P', 'Q', 'P', 'Q', 'R2', 'Coef' )

concisePermKernTable %>%
mutate(
    # this row needs to happen first, since the reformatting of the nother numbers makes them harder to call
    MDS1_Coef = cell_spec(format(MDS1_Coef, digits = 3), "html",
                           bold = ifelse(Kernel_P < 0.05, 
                                         T,
                                         F),
                          italic = ifelse(Kernel_P < 0.05 & MDS1_Coef < 0,
                                        T,
                                         F),
                          background = ifelse(Kernel_P < 0.05, ifelse(MDS1_Coef < 0, "lightsalmon", "lightblue"), "")
                         ),
    Kernel_P = cell_spec(format_round(Kernel_P, 3), "html",
                                  bold = ifelse(Kernel_P < 0.05, T, F),
                                  background = ifelse(Kernel_P < 0.05, 'yellow', '')
                                 ),
    Kernel_Q = cell_spec(format_round(Kernel_Q, 3), "html",
                                  bold = ifelse(Kernel_Q < 0.2, T, F),
                                  background = ifelse(Kernel_Q < 0.2, 'lightyellow', '')
                                 ),
     MDS1_P  = cell_spec(format_round(MDS1_P, 3), "html",
                                  bold = ifelse(MDS1_P < 0.05, T, F),
                                  background = ifelse(MDS1_P < 0.05, 'yellow', '')
                                 ),
    MDS1_Q = cell_spec(format_round(MDS1_Q, 3), "html",
                                  bold = ifelse(MDS1_Q < 0.2, T, F),
                                  background = ifelse(MDS1_Q < 0.2, 'lightyellow', '')
                                 ),
    #Month = cell_spec(format_round(Month,0), "html")
    Month = cell_spec(Month, "html")

    
      ) %>%
mutate(Antigen = gsub('ANY.ENV.PTEG', 'Any ENV PTEG', Antigen)) %>%
mutate(Antigen = gsub('gp70_B.CaseA_V1_V2', 'gp70 B.CaseA V1-V2', Antigen)) -> toTable

toTable %>%

kable("html", escape = F, digits = 3, align = 'c', col.names = colNames2) %>%
kable_styling("striped", "hover", full_width = F) %>%
add_header_above(colNames1) %>%
collapse_rows(columns = 1:2, latex_hline = "full") -> concisePermKernTable.html

concisePermKernTable.html

Kernel
MDS
Type Antigen Month P Q P Q R2 Coef
CD4+ Any ENV PTEG 6.5 0.249 0.498 0.218 0.125 0.093 -0.937
12 0.243 0.498 0.228 0.125 0.094 -0.920
IgA gp41 0 0.956 0.956 0.651 0.219 0.013 0.333
6.5 0.209 0.498 0.143 0.109 0.129 -1.133
12 0.744 0.930 0.855 0.259 0.002 -0.136
p24 0 0.896 0.952 0.320 0.161 0.061 0.752
6.5 0.904 0.952 0.650 0.219 0.013 -0.333
12 0.376 0.578 0.387 0.172 0.049 -0.657
IgG Con.6.gp120.B 6.5 0.004 0.076 0.002 0.011 0.497 -3.104
12 0.032 0.153 0.010 0.017 0.361 -2.289
gp41 0 0.046 0.153 0.014 0.017 0.331 2.185
6.5 0.046 0.153 0.030 0.031 0.267 -1.809
12 0.644 0.858 0.779 0.248 0.005 -0.205
gp70 B.CaseA V1-V2 6.5 0.904 0.952 0.619 0.219 0.016 -0.365
12 0.034 0.153 0.014 0.017 0.332 -2.135
p24 0 0.214 0.498 0.444 0.179 0.037 -0.567
6.5 0.425 0.608 0.397 0.172 0.047 -0.749
12 0.284 0.501 0.159 0.109 0.120 -1.085
ZM96.gp140 6.5 0.016 0.153 0.009 0.017 0.370 -2.338
12 0.300 0.501 0.162 0.109 0.118 -1.076


concisePermKernTable.html %>% cat(file = 'tables/concisePermkernTable.html')

Latex version of the same table


docHead <- "\\documentclass[12pt]{article} % use larger type; default would be 10pt

\\usepackage[utf8]{inputenc} % set input encoding (not needed with XeLaTeX)
\\usepackage{booktabs}
\\usepackage{longtable}
\\usepackage{array}
\\usepackage{multirow}
\\usepackage[table]{xcolor}
\\usepackage{wrapfig}
\\usepackage{float}
\\usepackage{colortbl}
\\usepackage{pdflscape}
\\usepackage{tabu}
\\usepackage{threeparttable}
\\usepackage{threeparttablex}
\\usepackage[normalem]{ulem}
\\usepackage{makecell}

\\definecolor{green}{rgb}{1, 1, .9}

\\begin{document}
"

docTail <- "\\end{document}
"
# Make latex table

concisePermKernTable %>%
mutate(
    # this row needs to happen first, since the reformatting of the nother numbers makes them harder to call
    MDS1_Coef = cell_spec(format(MDS1_Coef, digits = 3), "latex",
                           bold = ifelse(MDS1_P < 0.05, 
                                         T,
                                         F),
                          italic = ifelse(MDS1_P < 0.05 & MDS1_Coef < 0,
                                        T,
                                         F)),
    Kernel_P = cell_spec(format_round(Kernel_P, 3), "latex",
                                  bold = ifelse(Kernel_P < 0.05, T, F),
                                  background = ifelse(Kernel_P < 0.05, 'yellow', 'white')
                                 ),
    Kernel_Q = cell_spec(format_round(Kernel_Q, 3), "latex",
                                  bold = ifelse(Kernel_Q < 0.2, T, F),
                                  background = ifelse(Kernel_Q < 0.2, 'green', 'white')
                                 ),
     MDS1_P  = cell_spec(format_round(MDS1_P, 3), "latex",
                                  bold = ifelse(MDS1_P < 0.05, T, F),
                                  background = ifelse(MDS1_P < 0.05, 'yellow', 'white')
                                 ),
    MDS1_Q = cell_spec(format_round(MDS1_Q, 3), "latex",
                                  bold = ifelse(MDS1_Q < 0.2, T, F),
                                  background = ifelse(MDS1_Q < 0.2, 'green', 'white')
                                 ),
    #Month = cell_spec(format_round(Month,0), "html")
    Month = cell_spec(Month, "latex")

    
      ) %>%
mutate(Antigen = gsub('ANY.ENV.PTEG', 'Any ENV PTEG', Antigen)) %>%
mutate(Antigen = gsub('gp70_B.CaseA_V1_V2', 'gp70 B.CaseA V1-V2', Antigen)) -> toTable

toTable %>% 
kable("latex", escape = F, digits = 3, align = 'c', col.names = colNames2, booktabs = T) %>%
kable_styling(position = "left") %>%

add_header_above(colNames1) %>%
collapse_rows(columns = 1:2, latex_hline = "full") %>%
pass -> concisePermKernTable.tex
# Print latex table to tex file

cat(docHead, concisePermKernTable.tex, docTail, file = 'tables/concisePermkernTable1.tex')
concisePermKernTable %>% filter(Kernel_P < 0.05) -> shortPermkernTable
shortPermkernTable
write.csv(format(shortPermkernTable, digits = 3), 'tables/shortPermkernTable.csv')

Q-Q Plot

Of kernel regression P values

my_runif <- function(Len){
    loc_runif <- runif(n = Len)
    sort_loc_runif <- sort(loc_runif)
    data.frame(case = 1:Len, relement = sort_loc_runif)
}

calc_bound <- function(df, bound){
    quantile(df$relement, bound)
}

make_qqdata <- function(pvec, nboot = 10000){
    locP <- pvec

LP <- length(locP)
#1:10 %>% map(runif, n = LP)
sortedP <- sort(locP)
exp2 <- 1:length(locP)/length(locP)
sortedExpP <- sort(exp2)

random_pvalues <- data.frame(iter = 1:nboot) %>%
mutate(rand = map(iter, ~my_runif(Len = LP))) %>% unnest %>%
nest(-case) %>%
mutate(lb = map_dbl(data, ~calc_bound(., 0.025)),
      ub = map_dbl(data, ~calc_bound(.,0.975))) %>% dplyr::select (-data)

qqdata <- bind_cols(sortedP = sortedP, sortedExpP = sortedExpP, random_pvalues)

return(qqdata)
}
qqdata_permKernMir <- make_qqdata(permKernTable$mir.P)
qqdata_permKernMir %>% str
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   20 obs. of  5 variables:
 $ sortedP   : num  0.0038 0.0163 0.0324 0.0339 0.0457 ...
 $ sortedExpP: num  0.05 0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.5 ...
 $ case      : int  1 2 3 4 5 6 7 8 9 10 ...
 $ lb        : num  0.00128 0.01199 0.03215 0.05669 0.0869 ...
 $ ub        : num  0.166 0.253 0.317 0.378 0.437 ...
options(repr.plot.width=6, repr.plot.height= 6)
qqdata_permKernMir %>% ggplot(aes(x = sortedExpP, y = sortedP)) + geom_point(shape = 1) + geom_abline(intercept = 0, slope = 1) + 
labs(x = expression(paste("Expected ", italic("p"),"-value")), y = expression(paste("Observed ", italic("p"),"-value")))
ggsave('figures/qq_permKernMir.png')
Saving 7.29 x 4.5 in image

qqdata_permKernMir %>% ggplot(aes(x = sortedExpP, y = sortedP)) + geom_point(shape = 1) + geom_abline(intercept = 0, slope = 1) + geom_line(aes(y = lb), colour = "grey40") + geom_line(aes(y = ub), colour = "grey40") + scale_y_log10() + scale_x_log10()

We see from the qqplot that we generally fall below the 1:1 line, suggesting that our P values are lower than we would expect from chance. To make the grey lines, I sampled 95% confidence intervals of random draws from the uniform distribution (essentially random p values). The data points sometimes, but not always fall below the lower one of these. However, we care about all of the p values, not wheter eaach individual is more unusual than we would expect by chance. I’ll replort just the regular qqplot as a supplemental figure.

As above, but this time with gaussian - continuous dependent variables

ptm = proc.time()
tps <- CapVar(test.immune1, nperm = 9999, transformation = function(x){jac_box_cox_2(x)}, family = 'gaussian')
proc.time() - ptm
   user  system elapsed 
  0.650   0.004   0.654 
tps
# Run above function against every relevant variable.
ptm <- proc.time()

use.immune %>%
filter(ct == 'T') %>%
group_by(type, antigen, month) %>%
do(data.frame(CapVar(., nperm = jnperm,
                     transformation = function(x){jac_box_cox_2(x)},
                     family = 'gaussian'))) -> permKernTableGaus

|===================                                                                              | 20% ~10 s remaining    
|========================                                                                         | 25% ~10 s remaining    
|=============================                                                                    | 30% ~9 s remaining     
|=================================                                                                | 35% ~9 s remaining     
|======================================                                                           | 40% ~8 s remaining     
|===========================================                                                      | 45% ~7 s remaining     
|================================================                                                 | 50% ~7 s remaining     
|=====================================================                                            | 55% ~6 s remaining     
|==========================================================                                       | 60% ~5 s remaining     
|===============================================================                                  | 65% ~5 s remaining     
|===================================================================                              | 70% ~4 s remaining     
|========================================================================                         | 75% ~3 s remaining     
|=============================================================================                    | 80% ~3 s remaining     
|==================================================================================               | 85% ~2 s remaining     
|=======================================================================================          | 90% ~1 s remaining     
|============================================================================================     | 95% ~1 s remaining     
|=================================================================================================|100% ~0 s remaining     
permKernTableGaus

proc.time() - ptm
   user  system elapsed 
 13.442   0.036  13.436 
# Clean up so we just see the results of the kernel regression 
concisePermKernTableGaus <- permKernTableGaus %>% ungroup %>%
mutate(Kernel_Q = p2q(mir.P), MDS1_Q = p2q(wuf1.P)) %>%
dplyr::select(Type = type, Antigen = antigen, Month = month, Kernel_P = mir.P, Kernel_Q,
              MDS1_P = wuf1.P, MDS1_Q, MDS1_R2 = wuf1.McFadden, MDS1_Coef = wuf1.coef) %>%
as.data.frame %>% 
pass 

concisePermKernTableGaus

write.csv(format(concisePermKernTableGaus, digits = 3), 'tables/concisePermkernTableGaus.csv')

Table S1

# export conditionally formatted table as html
concisePermKernTableGaus %>%
mutate(
    # this row needs to happen first, since the reformatting of the nother numbers makes them harder to call
       MDS1_Coef = cell_spec(format(MDS1_Coef, digits = 3), "html",
                           bold = ifelse(Kernel_P < 0.05, 
                                         T,
                                         F),
                          italic = ifelse(Kernel_P < 0.05 & MDS1_Coef < 0,
                                        T,
                                         F),
                          background = ifelse(Kernel_P < 0.05, ifelse(MDS1_Coef < 0, "lightsalmon", "lightblue"), "")
                         ),
    Kernel_P = cell_spec(format_round(Kernel_P, 3), "html",
                                  bold = ifelse(Kernel_P < 0.05, T, F),
                                  background = ifelse(Kernel_P < 0.05, 'yellow', '')
                                 ),
    Kernel_Q = cell_spec(format_round(Kernel_Q, 3), "html",
                                  bold = ifelse(Kernel_Q < 0.2, T, F),
                                  background = ifelse(Kernel_Q < 0.2, 'lightyellow', '')
                                 ),
     MDS1_P  = cell_spec(format_round(MDS1_P, 3), "html",
                                  bold = ifelse(MDS1_P < 0.05, T, F),
                                  background = ifelse(MDS1_P < 0.05, 'yellow', '')
                                 ),
    MDS1_Q = cell_spec(format_round(MDS1_Q, 3), "html",
                                  bold = ifelse(MDS1_Q < 0.2, T, F),
                                  background = ifelse(MDS1_Q < 0.2, 'lightyellow', '')
                                 ),
    Month = cell_spec(Month, "html")

    
      ) %>%

mutate(Antigen = gsub('ANY.ENV.PTEG', 'Any ENV PTEG', Antigen)) %>%
mutate(Antigen = gsub('gp70_B.CaseA_V1_V2', 'gp70 B.CaseA V1-V2', Antigen)) -> toTable

toTable %>%

kable("html", escape = F, digits = 3, align = 'c', col.names = colNames2) %>%
kable_styling("striped", "hover", full_width = F) %>%
add_header_above(colNames1) %>%
collapse_rows(columns = 1:2, latex_hline = "full") -> concisePermKernTableGaus.html

concisePermKernTableGaus.html

Kernel
MDS
Type Antigen Month P Q P Q R2 Coef
CD4+ Any ENV PTEG 6.5 0.610 0.813 0.597 0.344 0.015 -0.1962
12 0.183 0.488 0.329 0.248 0.054 -0.3562
IgA gp41 0 0.989 0.989 0.633 0.344 0.013 0.1775
6.5 0.407 0.626 0.601 0.344 0.015 -0.1944
12 0.652 0.814 0.504 0.329 0.026 0.2516
p24 0 0.951 0.989 0.433 0.303 0.033 0.2885
6.5 0.977 0.989 0.857 0.441 0.002 -0.0672
12 0.577 0.813 0.309 0.248 0.058 -0.3773
IgG Con.6.gp120.B 6.5 0.075 0.374 0.032 0.093 0.208 -0.7209
12 0.001 0.018 0.000 0.000 0.530 -1.1502
gp41 0 0.220 0.488 0.038 0.093 0.197 0.7009
6.5 0.212 0.488 0.080 0.102 0.148 -0.6081
12 0.257 0.505 0.173 0.169 0.095 -0.4863
gp70 B.CaseA V1-V2 6.5 0.278 0.505 0.206 0.183 0.083 -0.4540
12 0.108 0.434 0.083 0.102 0.145 -0.6018
p24 0 0.757 0.890 0.907 0.444 0.001 0.0437
6.5 0.025 0.197 0.053 0.102 0.184 -0.7866
12 0.407 0.626 0.133 0.145 0.113 -0.5307
ZM96.gp140 6.5 0.030 0.197 0.017 0.083 0.246 -0.7835
12 0.199 0.488 0.078 0.102 0.150 -0.6117


concisePermKernTableGaus.html %>% cat(file = 'tables/concisePermkernTableGaus.html')
concisePermKernTableGaus %>% filter(Kernel_P < 0.05) -> shortPermkernTableGaus
shortPermkernTableGaus
write.csv(format(shortPermkernTableGaus, digits = 3), 'tables/shortPermkernTable.csv')
# Make latex table

concisePermKernTableGaus %>%
mutate(
    # this row needs to happen first, since the reformatting of the nother numbers makes them harder to call
    MDS1_Coef = cell_spec(format(MDS1_Coef, digits = 3), "html",
                           bold = ifelse(Kernel_P < 0.05, 
                                         T,
                                         F),
                          italic = ifelse(Kernel_P < 0.05 & MDS1_Coef < 0,
                                        T,
                                         F),
                          background = ifelse(Kernel_P < 0.05, ifelse(MDS1_Coef < 0, "lightsalmon", "lightblue"), "")
                         ),
    Kernel_P = cell_spec(format_round(Kernel_P, 3), "latex",
                                  bold = ifelse(Kernel_P < 0.05, T, F),
                                  background = ifelse(Kernel_P < 0.05, 'yellow', 'white')
                                 ),
    Kernel_Q = cell_spec(format_round(Kernel_Q, 3), "latex",
                                  bold = ifelse(Kernel_Q < 0.2, T, F),
                                  background = ifelse(Kernel_Q < 0.2, 'green', 'white')
                                 ),
     MDS1_P  = cell_spec(format_round(MDS1_P, 3), "latex",
                                  bold = ifelse(MDS1_P < 0.05, T, F),
                                  background = ifelse(MDS1_P < 0.05, 'yellow', 'white')
                                 ),
    MDS1_Q = cell_spec(format_round(MDS1_Q, 3), "latex",
                                  bold = ifelse(MDS1_Q < 0.2, T, F),
                                  background = ifelse(MDS1_Q < 0.2, 'green', 'white')
                                 ),
    #Month = cell_spec(format_round(Month,0), "html")
    Month = cell_spec(Month, "latex")

    
      ) %>%
mutate(Antigen = gsub('ANY.ENV.PTEG', 'Any ENV PTEG', Antigen)) %>%
mutate(Antigen = gsub('gp70_B.CaseA_V1_V2', 'gp70 B.CaseA V1-V2', Antigen)) -> toTable

toTable %>% 
kable("latex", escape = F, digits = 3, align = 'c', col.names = colNames2, booktabs = T) %>%
kable_styling(position = "left") %>%

add_header_above(colNames1) %>%
collapse_rows(columns = 1:2, latex_hline = "full") %>%
pass -> concisePermKernTableGaus.tex
# Print latex table to tex file

cat(docHead, concisePermKernTableGaus.tex, docTail, file = 'tables/concisePermkernTableGaus.tex')
### QQplot for kernel regression data
qqdata_permKernMirGaus <- make_qqdata(permKernTableGaus$mir.P)
options(repr.plot.width=6, repr.plot.height= 6)
qqdata_permKernMirGaus %>% ggplot(aes(x = sortedExpP, y = sortedP)) + geom_point(shape = 1) + geom_abline(intercept = 0, slope = 1) + 
labs(x = expression(paste("Expected ", italic("p"),"-value")), y = expression(paste("Observed ", italic("p"),"-value")))
ggsave('figures/qq_permKernMirGaus.png')
Saving 7.29 x 4.5 in image

Chi Squared test for statistical associations between each pair of immune variables

use.immune %>% dplyr::select(pub_id, visitno, type, antigen, mag) -> tmp
full_join(tmp, tmp, by = 'pub_id') %>% 
group_by(visitno.x, type.x, antigen.x, visitno.y, type.y, antigen.y) %>%
nest %>%
mutate(x2 = map(data, function(df){unwarn(chisq.test(df$mag.x, df$mag.y))})) %>%
mutate(glance = map(x2, glance)) %>%
dplyr::select(-data, -x2) %>%
unnest(glance) %>%
#mutate(q.value = p2q(p.value)) %>% # reurns NaNs
pass -> compareImmuneX2
compareImmuneX2 %>% filter(
    type.x == 'IgG' &
    antigen.x == 'gp41' &
    type.y == 'IgG' &
    antigen.y == 'gp41'
)
compareImmuneX2 %>%
filter(type.x == 'IgG' & type.y == 'IgG' & antigen.x != antigen.y) %>%
write_csv('tables/chisq_IgG_comparasons.csv')

MDS GLM for each other MDS Axis

psN2 %>% phylo_join(scores(psN2.pcoa, display = "sites", choices = 1:10) %>%
                    as.data.frame %>% rownames_to_column, by = 'rowname') %>%
sample_data %>% as('data.frame') %>% rownames_to_column -> hereSam
data_frame(formula = paste("transformation(ydata) ~ MDS", 1:10, sep = ""))
EachMDS <- function(x, nperm = 9999, transformation = medcode2, family = 'binomial'){
    ## Pull out the needed data
    
    psN2.wMDS <- psN2 %>% phylo_join(scores(psN2.pcoa, display = "sites", choices = 1:10) %>%
                    as.data.frame %>% rownames_to_column, by = 'rowname')
    
#     medWuf <- NA
#     rankWuf <- NA
    locPS <- phylo_join(psN2.wMDS, x, by = 'pub_id') 
    ydata0 <- sample_data(locPS)$mag
    yna <- is.na(ydata0)
    #loc.wuf <- wufKN2
    #loc.jsd <- jsdKN2
    ydata <- ydata0
    
    ydata <- ydata0[!yna]
    loc.wuf2 <- psN2.wuf %>% as.matrix %>% .[!yna, !yna]
    
     samDf <- locPS %>% sample_data %>% as('data.frame') %>% rownames_to_column %>%
    .[!yna,]

#     # Is giving only positive results with CAP1, not sure why
    loc_glm <- glm(as.formula("transformation(ydata) ~  MDS1"), data = samDf, family = family)
    glmAnova <- loc_glm %>% anova(test = "Chisq")
    
    # data_frame, rather than data.frame
    # https://stackoverflow.com/questions/48450308/iterating-over-formulas-in-purrr#48450308
    data_frame(formulaString = paste("transformation(ydata) ~ MDS", 1:10, sep = "")) %>%
     mutate(model = map(formulaString, function(fs){
         glm(as.formula(fs), data = samDf, family = family)})) %>%
    mutate(anova = map(model, anova)) %>%
    mutate(glance = map(model, glance)) %>%
    mutate(tidy = map(model, tidy)) %>%
    mutate(coef = map(model, ~ coef(summary(.))[2,])) %>%
    pass -> allmodels

    allmodels %>% dplyr::select("tidy") %>% unnest %>% filter(term != '(Intercept)')
    
 
    }
    
# Just confirming that the function works before it goes in a giant loop. I'd delete this,
# but i'll just end up needing it again if I do.
ptm = proc.time()
tps <- EachMDS(test.immune.pteg, nperm = 9999, transformation = medcode, family = 'binomial')
proc.time() - ptm
   user  system elapsed 
  0.169   0.000   0.168 
tps
use.immune %>%
group_by(type, antigen, month) %>%
nest %>%
mutate(coefs = map(data, ~ EachMDS(.))) %>%
dplyr::select(-data) %>% unnest(coefs) -> glmMDScoefs
ants1
[1] "Con.6.gp120.B"      "ZM96.gp140"         "gp70_B.CaseA_V1_V2"
glmMDScoefs %>%
gather(key = "key", value = "value", estimate:p.value) %>%
filter(key == "p.value") %>%
spread(key = term, value = value) %>%
dplyr::select(-key, -MDS10, MDS10) %>%
dplyr::rename(Type = type, Antigen = antigen, Month = month) %>%
mutate(Type = factor(Type, levels = c( "IgA", "IgG",  "CD4+"))) %>%
mutate(Antigen = factor(Antigen, levels = c(ants1, ants2, "ANY.ENV.PTEG"))) %>%
#Clean up labels
mutate(Antigen = stringr::str_replace_all(Antigen, "_", " ")) %>%
mutate(Antigen = stringr::str_replace_all(Antigen, "V1 V2", "V1-V2")) %>%
mutate(Antigen = stringr::str_replace_all(Antigen, "ANY.ENV.PTEG", "Any ENV PTEG")) %>%
arrange(Type) -> allMDS
allMDS

Table S2

allMDS %>%
kable("html", escape = F, digits = 3, align = 'c') %>%
collapse_rows(columns = 1:2, latex_hline = "full") %>%
as.character() -> allMDS.html

allMDS %>%
kable("html", escape = F, digits = 3, align = 'c') %>%
collapse_rows(columns = 1:2, latex_hline = "full")

Type Antigen Month MDS1 MDS2 MDS3 MDS4 MDS5 MDS6 MDS7 MDS8 MDS9 MDS10
IgA gp41 0.0 0.653 0.607 0.401 0.702 0.786 0.650 0.242 0.349 0.038 0.700
6.5 0.168 0.368 0.701 0.954 0.112 0.142 0.456 0.475 0.789 0.864
12.0 0.855 0.201 0.462 0.408 0.994 0.798 0.469 0.106 0.079 0.232
p24 0.0 0.335 0.629 0.520 0.939 0.666 0.657 0.148 0.853 0.921 0.517
6.5 0.652 0.281 0.681 0.414 0.438 0.599 0.999 0.199 0.881 0.351
12.0 0.398 0.724 0.227 0.202 0.128 0.465 0.445 0.156 0.516 0.159
IgG Con.6.gp120.B 6.5 0.017 0.536 0.420 0.890 0.325 0.427 0.853 0.806 0.634 0.250
12.0 0.031 0.878 0.377 0.635 0.989 0.540 0.569 0.374 0.686 0.299
gp41 0.0 0.040 0.894 0.304 0.967 0.442 0.231 0.504 0.595 0.856 0.509
6.5 0.057 0.274 0.226 0.891 0.858 0.165 0.601 0.426 0.212 0.606
12.0 0.779 0.310 0.443 0.228 0.680 0.268 0.995 0.039 0.357 0.054
gp70 B.CaseA V1-V2 6.5 0.621 0.853 0.300 0.458 0.507 0.690 0.506 0.161 0.039 0.880
12.0 0.037 0.665 0.808 0.447 0.318 0.296 0.743 0.143 0.403 0.276
p24 0.0 0.451 0.231 0.986 0.088 0.149 0.051 0.882 0.507 0.384 0.439
6.5 0.404 0.828 0.243 0.250 0.689 0.082 0.180 0.846 0.360 0.101
12.0 0.182 0.721 0.237 0.676 0.933 0.371 0.042 0.423 0.716 0.147
ZM96.gp140 6.5 0.030 0.631 0.244 0.750 0.234 0.949 0.890 0.994 0.090 0.504
12.0 0.186 0.353 0.286 0.722 0.889 0.409 0.459 0.376 0.021 0.189
CD4+ Any ENV PTEG 6.5 0.237 0.555 0.771 0.108 0.237 0.104 0.717 0.084 0.627 0.319
12.0 0.248 0.565 0.434 0.103 0.117 0.856 0.861 0.128 0.773 0.723



allMDS.html
[1] "<table>\n <thead>\n  <tr>\n   <th style=\"text-align:center;\"> Type </th>\n   <th style=\"text-align:center;\"> Antigen </th>\n   <th style=\"text-align:center;\"> Month </th>\n   <th style=\"text-align:center;\"> MDS1 </th>\n   <th style=\"text-align:center;\"> MDS2 </th>\n   <th style=\"text-align:center;\"> MDS3 </th>\n   <th style=\"text-align:center;\"> MDS4 </th>\n   <th style=\"text-align:center;\"> MDS5 </th>\n   <th style=\"text-align:center;\"> MDS6 </th>\n   <th style=\"text-align:center;\"> MDS7 </th>\n   <th style=\"text-align:center;\"> MDS8 </th>\n   <th style=\"text-align:center;\"> MDS9 </th>\n   <th style=\"text-align:center;\"> MDS10 </th>\n  </tr>\n </thead>\n<tbody>\n  <tr>\n   <td style=\"text-align:center;vertical-align: middle !important;\" rowspan=\"6\"> IgA </td>\n   <td style=\"text-align:center;vertical-align: middle !important;\" rowspan=\"3\"> gp41 </td>\n   <td style=\"text-align:center;\"> 0.0 </td>\n   <td style=\"text-align:center;\"> 0.653 </td>\n   <td style=\"text-align:center;\"> 0.607 </td>\n   <td style=\"text-align:center;\"> 0.401 </td>\n   <td style=\"text-align:center;\"> 0.702 </td>\n   <td style=\"text-align:center;\"> 0.786 </td>\n   <td style=\"text-align:center;\"> 0.650 </td>\n   <td style=\"text-align:center;\"> 0.242 </td>\n   <td style=\"text-align:center;\"> 0.349 </td>\n   <td style=\"text-align:center;\"> 0.038 </td>\n   <td style=\"text-align:center;\"> 0.700 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 6.5 </td>\n   <td style=\"text-align:center;\"> 0.168 </td>\n   <td style=\"text-align:center;\"> 0.368 </td>\n   <td style=\"text-align:center;\"> 0.701 </td>\n   <td style=\"text-align:center;\"> 0.954 </td>\n   <td style=\"text-align:center;\"> 0.112 </td>\n   <td style=\"text-align:center;\"> 0.142 </td>\n   <td style=\"text-align:center;\"> 0.456 </td>\n   <td style=\"text-align:center;\"> 0.475 </td>\n   <td style=\"text-align:center;\"> 0.789 </td>\n   <td style=\"text-align:center;\"> 0.864 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 12.0 </td>\n   <td style=\"text-align:center;\"> 0.855 </td>\n   <td style=\"text-align:center;\"> 0.201 </td>\n   <td style=\"text-align:center;\"> 0.462 </td>\n   <td style=\"text-align:center;\"> 0.408 </td>\n   <td style=\"text-align:center;\"> 0.994 </td>\n   <td style=\"text-align:center;\"> 0.798 </td>\n   <td style=\"text-align:center;\"> 0.469 </td>\n   <td style=\"text-align:center;\"> 0.106 </td>\n   <td style=\"text-align:center;\"> 0.079 </td>\n   <td style=\"text-align:center;\"> 0.232 </td>\n  </tr>\n  <tr>\n   \n   <td style=\"text-align:center;vertical-align: middle !important;\" rowspan=\"3\"> p24 </td>\n   <td style=\"text-align:center;\"> 0.0 </td>\n   <td style=\"text-align:center;\"> 0.335 </td>\n   <td style=\"text-align:center;\"> 0.629 </td>\n   <td style=\"text-align:center;\"> 0.520 </td>\n   <td style=\"text-align:center;\"> 0.939 </td>\n   <td style=\"text-align:center;\"> 0.666 </td>\n   <td style=\"text-align:center;\"> 0.657 </td>\n   <td style=\"text-align:center;\"> 0.148 </td>\n   <td style=\"text-align:center;\"> 0.853 </td>\n   <td style=\"text-align:center;\"> 0.921 </td>\n   <td style=\"text-align:center;\"> 0.517 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 6.5 </td>\n   <td style=\"text-align:center;\"> 0.652 </td>\n   <td style=\"text-align:center;\"> 0.281 </td>\n   <td style=\"text-align:center;\"> 0.681 </td>\n   <td style=\"text-align:center;\"> 0.414 </td>\n   <td style=\"text-align:center;\"> 0.438 </td>\n   <td style=\"text-align:center;\"> 0.599 </td>\n   <td style=\"text-align:center;\"> 0.999 </td>\n   <td style=\"text-align:center;\"> 0.199 </td>\n   <td style=\"text-align:center;\"> 0.881 </td>\n   <td style=\"text-align:center;\"> 0.351 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 12.0 </td>\n   <td style=\"text-align:center;\"> 0.398 </td>\n   <td style=\"text-align:center;\"> 0.724 </td>\n   <td style=\"text-align:center;\"> 0.227 </td>\n   <td style=\"text-align:center;\"> 0.202 </td>\n   <td style=\"text-align:center;\"> 0.128 </td>\n   <td style=\"text-align:center;\"> 0.465 </td>\n   <td style=\"text-align:center;\"> 0.445 </td>\n   <td style=\"text-align:center;\"> 0.156 </td>\n   <td style=\"text-align:center;\"> 0.516 </td>\n   <td style=\"text-align:center;\"> 0.159 </td>\n  </tr>\n  <tr>\n   <td style=\"text-align:center;vertical-align: middle !important;\" rowspan=\"12\"> IgG </td>\n   <td style=\"text-align:center;vertical-align: middle !important;\" rowspan=\"2\"> Con.6.gp120.B </td>\n   <td style=\"text-align:center;\"> 6.5 </td>\n   <td style=\"text-align:center;\"> 0.017 </td>\n   <td style=\"text-align:center;\"> 0.536 </td>\n   <td style=\"text-align:center;\"> 0.420 </td>\n   <td style=\"text-align:center;\"> 0.890 </td>\n   <td style=\"text-align:center;\"> 0.325 </td>\n   <td style=\"text-align:center;\"> 0.427 </td>\n   <td style=\"text-align:center;\"> 0.853 </td>\n   <td style=\"text-align:center;\"> 0.806 </td>\n   <td style=\"text-align:center;\"> 0.634 </td>\n   <td style=\"text-align:center;\"> 0.250 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 12.0 </td>\n   <td style=\"text-align:center;\"> 0.031 </td>\n   <td style=\"text-align:center;\"> 0.878 </td>\n   <td style=\"text-align:center;\"> 0.377 </td>\n   <td style=\"text-align:center;\"> 0.635 </td>\n   <td style=\"text-align:center;\"> 0.989 </td>\n   <td style=\"text-align:center;\"> 0.540 </td>\n   <td style=\"text-align:center;\"> 0.569 </td>\n   <td style=\"text-align:center;\"> 0.374 </td>\n   <td style=\"text-align:center;\"> 0.686 </td>\n   <td style=\"text-align:center;\"> 0.299 </td>\n  </tr>\n  <tr>\n   \n   <td style=\"text-align:center;vertical-align: middle !important;\" rowspan=\"3\"> gp41 </td>\n   <td style=\"text-align:center;\"> 0.0 </td>\n   <td style=\"text-align:center;\"> 0.040 </td>\n   <td style=\"text-align:center;\"> 0.894 </td>\n   <td style=\"text-align:center;\"> 0.304 </td>\n   <td style=\"text-align:center;\"> 0.967 </td>\n   <td style=\"text-align:center;\"> 0.442 </td>\n   <td style=\"text-align:center;\"> 0.231 </td>\n   <td style=\"text-align:center;\"> 0.504 </td>\n   <td style=\"text-align:center;\"> 0.595 </td>\n   <td style=\"text-align:center;\"> 0.856 </td>\n   <td style=\"text-align:center;\"> 0.509 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 6.5 </td>\n   <td style=\"text-align:center;\"> 0.057 </td>\n   <td style=\"text-align:center;\"> 0.274 </td>\n   <td style=\"text-align:center;\"> 0.226 </td>\n   <td style=\"text-align:center;\"> 0.891 </td>\n   <td style=\"text-align:center;\"> 0.858 </td>\n   <td style=\"text-align:center;\"> 0.165 </td>\n   <td style=\"text-align:center;\"> 0.601 </td>\n   <td style=\"text-align:center;\"> 0.426 </td>\n   <td style=\"text-align:center;\"> 0.212 </td>\n   <td style=\"text-align:center;\"> 0.606 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 12.0 </td>\n   <td style=\"text-align:center;\"> 0.779 </td>\n   <td style=\"text-align:center;\"> 0.310 </td>\n   <td style=\"text-align:center;\"> 0.443 </td>\n   <td style=\"text-align:center;\"> 0.228 </td>\n   <td style=\"text-align:center;\"> 0.680 </td>\n   <td style=\"text-align:center;\"> 0.268 </td>\n   <td style=\"text-align:center;\"> 0.995 </td>\n   <td style=\"text-align:center;\"> 0.039 </td>\n   <td style=\"text-align:center;\"> 0.357 </td>\n   <td style=\"text-align:center;\"> 0.054 </td>\n  </tr>\n  <tr>\n   \n   <td style=\"text-align:center;vertical-align: middle !important;\" rowspan=\"2\"> gp70 B.CaseA V1-V2 </td>\n   <td style=\"text-align:center;\"> 6.5 </td>\n   <td style=\"text-align:center;\"> 0.621 </td>\n   <td style=\"text-align:center;\"> 0.853 </td>\n   <td style=\"text-align:center;\"> 0.300 </td>\n   <td style=\"text-align:center;\"> 0.458 </td>\n   <td style=\"text-align:center;\"> 0.507 </td>\n   <td style=\"text-align:center;\"> 0.690 </td>\n   <td style=\"text-align:center;\"> 0.506 </td>\n   <td style=\"text-align:center;\"> 0.161 </td>\n   <td style=\"text-align:center;\"> 0.039 </td>\n   <td style=\"text-align:center;\"> 0.880 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 12.0 </td>\n   <td style=\"text-align:center;\"> 0.037 </td>\n   <td style=\"text-align:center;\"> 0.665 </td>\n   <td style=\"text-align:center;\"> 0.808 </td>\n   <td style=\"text-align:center;\"> 0.447 </td>\n   <td style=\"text-align:center;\"> 0.318 </td>\n   <td style=\"text-align:center;\"> 0.296 </td>\n   <td style=\"text-align:center;\"> 0.743 </td>\n   <td style=\"text-align:center;\"> 0.143 </td>\n   <td style=\"text-align:center;\"> 0.403 </td>\n   <td style=\"text-align:center;\"> 0.276 </td>\n  </tr>\n  <tr>\n   \n   <td style=\"text-align:center;vertical-align: middle !important;\" rowspan=\"3\"> p24 </td>\n   <td style=\"text-align:center;\"> 0.0 </td>\n   <td style=\"text-align:center;\"> 0.451 </td>\n   <td style=\"text-align:center;\"> 0.231 </td>\n   <td style=\"text-align:center;\"> 0.986 </td>\n   <td style=\"text-align:center;\"> 0.088 </td>\n   <td style=\"text-align:center;\"> 0.149 </td>\n   <td style=\"text-align:center;\"> 0.051 </td>\n   <td style=\"text-align:center;\"> 0.882 </td>\n   <td style=\"text-align:center;\"> 0.507 </td>\n   <td style=\"text-align:center;\"> 0.384 </td>\n   <td style=\"text-align:center;\"> 0.439 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 6.5 </td>\n   <td style=\"text-align:center;\"> 0.404 </td>\n   <td style=\"text-align:center;\"> 0.828 </td>\n   <td style=\"text-align:center;\"> 0.243 </td>\n   <td style=\"text-align:center;\"> 0.250 </td>\n   <td style=\"text-align:center;\"> 0.689 </td>\n   <td style=\"text-align:center;\"> 0.082 </td>\n   <td style=\"text-align:center;\"> 0.180 </td>\n   <td style=\"text-align:center;\"> 0.846 </td>\n   <td style=\"text-align:center;\"> 0.360 </td>\n   <td style=\"text-align:center;\"> 0.101 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 12.0 </td>\n   <td style=\"text-align:center;\"> 0.182 </td>\n   <td style=\"text-align:center;\"> 0.721 </td>\n   <td style=\"text-align:center;\"> 0.237 </td>\n   <td style=\"text-align:center;\"> 0.676 </td>\n   <td style=\"text-align:center;\"> 0.933 </td>\n   <td style=\"text-align:center;\"> 0.371 </td>\n   <td style=\"text-align:center;\"> 0.042 </td>\n   <td style=\"text-align:center;\"> 0.423 </td>\n   <td style=\"text-align:center;\"> 0.716 </td>\n   <td style=\"text-align:center;\"> 0.147 </td>\n  </tr>\n  <tr>\n   \n   <td style=\"text-align:center;vertical-align: middle !important;\" rowspan=\"2\"> ZM96.gp140 </td>\n   <td style=\"text-align:center;\"> 6.5 </td>\n   <td style=\"text-align:center;\"> 0.030 </td>\n   <td style=\"text-align:center;\"> 0.631 </td>\n   <td style=\"text-align:center;\"> 0.244 </td>\n   <td style=\"text-align:center;\"> 0.750 </td>\n   <td style=\"text-align:center;\"> 0.234 </td>\n   <td style=\"text-align:center;\"> 0.949 </td>\n   <td style=\"text-align:center;\"> 0.890 </td>\n   <td style=\"text-align:center;\"> 0.994 </td>\n   <td style=\"text-align:center;\"> 0.090 </td>\n   <td style=\"text-align:center;\"> 0.504 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 12.0 </td>\n   <td style=\"text-align:center;\"> 0.186 </td>\n   <td style=\"text-align:center;\"> 0.353 </td>\n   <td style=\"text-align:center;\"> 0.286 </td>\n   <td style=\"text-align:center;\"> 0.722 </td>\n   <td style=\"text-align:center;\"> 0.889 </td>\n   <td style=\"text-align:center;\"> 0.409 </td>\n   <td style=\"text-align:center;\"> 0.459 </td>\n   <td style=\"text-align:center;\"> 0.376 </td>\n   <td style=\"text-align:center;\"> 0.021 </td>\n   <td style=\"text-align:center;\"> 0.189 </td>\n  </tr>\n  <tr>\n   <td style=\"text-align:center;vertical-align: middle !important;\" rowspan=\"2\"> CD4+ </td>\n   <td style=\"text-align:center;vertical-align: middle !important;\" rowspan=\"2\"> Any ENV PTEG </td>\n   <td style=\"text-align:center;\"> 6.5 </td>\n   <td style=\"text-align:center;\"> 0.237 </td>\n   <td style=\"text-align:center;\"> 0.555 </td>\n   <td style=\"text-align:center;\"> 0.771 </td>\n   <td style=\"text-align:center;\"> 0.108 </td>\n   <td style=\"text-align:center;\"> 0.237 </td>\n   <td style=\"text-align:center;\"> 0.104 </td>\n   <td style=\"text-align:center;\"> 0.717 </td>\n   <td style=\"text-align:center;\"> 0.084 </td>\n   <td style=\"text-align:center;\"> 0.627 </td>\n   <td style=\"text-align:center;\"> 0.319 </td>\n  </tr>\n  <tr>\n   \n   \n   <td style=\"text-align:center;\"> 12.0 </td>\n   <td style=\"text-align:center;\"> 0.248 </td>\n   <td style=\"text-align:center;\"> 0.565 </td>\n   <td style=\"text-align:center;\"> 0.434 </td>\n   <td style=\"text-align:center;\"> 0.103 </td>\n   <td style=\"text-align:center;\"> 0.117 </td>\n   <td style=\"text-align:center;\"> 0.856 </td>\n   <td style=\"text-align:center;\"> 0.861 </td>\n   <td style=\"text-align:center;\"> 0.128 </td>\n   <td style=\"text-align:center;\"> 0.773 </td>\n   <td style=\"text-align:center;\"> 0.723 </td>\n  </tr>\n</tbody>\n</table>"
allMDS.html %>% cat(file = 'tables/allMDS.html')
allMDS %>%
kable("latex", escape = F, digits = 3, align = 'c', booktabs = T) %>%
collapse_rows(columns = 1:2, latex_hline = "full") %>%
pass -> allMDS.latex


allMDS.latex %>% cat(file = 'tables/allMDS.tex')
# Print latex table to tex file
cat(docHead, allMDS.latex, docTail, file = 'tables/allMDS.tex')
write_csv(allMDS, 'tables/allMDSGlmPValues.csv')

Jensen Shannon Kernel Regression

at each taxonomic level

Agglomeration

# How many taxa do we see if we agglomerate at different levels
psN2 %>% tax_table %>% as.data.frame %>% dplyr::select(Phylum:Genus) %>% colnames -> taxLevels

data_frame(taxLevels) %>%
mutate(ntaxa = map(taxLevels,
    function(lev){
        psN2 %>% tax_glom(lev) %>% ntaxa
    }
                                             )) %>%
mutate(ntaxa = unlist(ntaxa)) %>%
pass -> NTaxaAtLevel
NTaxaAtLevel
data_frame(taxLevels = "Species", ntaxa = ntaxa(psN2), ps = list((psN2))) -> specRow
data_frame(taxLevels = "Species", ntaxa = ntaxa(psN1), psCount = list((psN1))) -> specRowC
D2K_savename <- function(distmat){
    # cascade names forward with the D2K operation
    require(MiRKAT)
    out <- MiRKAT::D2K(distmat)
    colnames(out) <- colnames(distmat)
    rownames(out) <- rownames(distmat)
    out
}
# Data frame of phyloseq objects distances and kernels at a bunch of taxonomic levels
NTaxaAtLevel %>%
mutate(ps = map(ntaxa, ~tip_glom_saveid(psN2, k = .))) %>%
# process the phyloseq objects so they have better names
mutate(ps = map(ps, ~swap.phyloseq.taxnames(tag_phyloseq(remove_tag_phyloseq(.)), oldname = 'oldname2'))) %>%
# add in the species data row (which should already have correct names)
bind_rows(specRow) %>%
# calculate jensen-shannon distance matrix
mutate(jsd = map(ps, ~phyloseq::distance(., method = "jsd") )) %>%
# convert to 2d matrix
mutate(jsdMat = map(jsd, ~as.matrix(.))) %>%
# calculate kernel
mutate(kjsd = map(jsdMat, ~D2K_savename(.))) -> tmp
Loading required package: cluster
Setting row names on a tibble is deprecated.Setting row names on a tibble is deprecated.Setting row names on a tibble is deprecated.Setting row names on a tibble is deprecated.Setting row names on a tibble is deprecated.
tmp %>%
mutate(psNoZero = map(ps, ~transform_sample_counts(., function(x) x+(1/1000)))) -> tmp

tmp %>%
## chemometrics::clr just works, while compositions::clr throws a criptic error message here
mutate(clr = map(psNoZero, ~ transform_otu_table(., chemometrics::clr))) %>%
#mutate(clr = map(psNoZero, ~ transform_otu_table(., function(x) as.matrix(compositions::clr(x))))) %>%
pass -> psDf0 # Original way
# Data frame of phyloseq objects distances and kernels at a bunch of taxonomic levels
# I use psN1 because I need count data for some downstream steps.
NTaxaAtLevel %>%
mutate(psCount = map(ntaxa, ~tip_glom_saveid(psN1, k = .))) %>%
# process the phyloseq objects so they have better names
mutate(psCount = map(psCount, ~swap.phyloseq.taxnames(tag_phyloseq(remove_tag_phyloseq(.)), oldname = 'oldname2'))) %>%
# add in the species data row (which should already have correct names)
bind_rows(specRowC) %>%
pass -> tmp
Setting row names on a tibble is deprecated.Setting row names on a tibble is deprecated.Setting row names on a tibble is deprecated.Setting row names on a tibble is deprecated.Setting row names on a tibble is deprecated.
tmp %>%
# calculate jensen-shannon distance matrix
mutate(ps = map(psCount, ~transform_sample_counts(., function(x) {x/sum(x)}))) %>%
mutate(jsd = map(ps, ~phyloseq::distance(., method = "jsd") )) %>%
# convert to 2d matrix
mutate(jsdMat = map(jsd, ~as.matrix(.))) %>%
# calculate kernel
mutate(kjsd = map(jsdMat, ~D2K_savename(.))) -> tmp

tmp %>%
mutate(psNoZero = map(ps, ~transform_sample_counts(., function(x) x+(1/1000)))) %>%
## chemometrics::clr just works, while compositions::clr throws a criptic error message here
mutate(clr = map(psNoZero, ~ transform_otu_table(., chemometrics::clr))) %>%
#mutate(clr = map(psNoZero, ~ transform_otu_table(., function(x) as.matrix(compositions::clr(x))))) %>%
pass -> psDf
print(psDf)
MirMulti <- function(x, KsDf = psDf, ps = psN2, nperm = 9999){
    
    Ks = KsDf$kjsd
    
    # I  bind to the phyloseq object and then peel off again later to guerentee
    # that the y-data is in the same order as the Ks
    locPS <- phylo_join(ps, x, by = 'pub_id')
    
    ydata0 <- sample_data(locPS)$mag
    yna <- is.na(ydata0)

    ydata <- ydata0[!yna]
    loc.Ks <- lapply(Ks, function(K){K[!yna, !yna]})  
  
    bcxJSD <- MiRKAT(y = jac_box_cox_2(ydata), Ks = loc.Ks, out_type = "C", method = 'permutation', nperm = nperm)
    medJSD <- MiRKAT(y = medcode(ydata), Ks = loc.Ks, out_type = "D", method = 'permutation', nperm = nperm)
    mmDf = data.frame(
        taxLevels = KsDf$taxLevels,
        ntaxa = KsDf$ntaxa,
        bcxJSD = bcxJSD$indivP, medJSD = medJSD$indivP,
        bcxJSDOmni = bcxJSD$omnibus_p, medJSDOmni = medJSD$omnibus_p)
    mmDf
    
    }
# Test case

use.immune %>%
filter(type == 'IgG' & antigen == 'gp41'& visitno == 2 & ct == 'T') -> test.immune1

test.mm <- MirMulti(test.immune1, Ks = psDf, nperm = 999)

test.mm
ptm = proc.time()

use.immune %>%
group_by(type, antigen, month) %>%
nest %>%
mutate(mir = map(data,
    ~MirMulti(., Ks = psDf, ps = psN2, nperm = 999)
)) %>%
dplyr::select(-data) %>% unnest(mir) %>%
pass -> mirLevels

proc.time() - ptm
   user  system elapsed 
  2.592   0.024   2.615 
mirLevels %>% dplyr::select(-ntaxa, -medJSD) %>% spread(key = taxLevels, value = bcxJSD)
mirLevels %>% dplyr::select(-ntaxa, -bcxJSD) %>% spread(key = taxLevels, value = medJSD)

I’d like to combine the above into one table, when it isn’t 7:45. Probably has soemething to do with merging columns or something. Or maybe I just want to plot it as a figure.

mirLevels %>%
gather(metric, P, bcxJSD:medJSD) -> mirDat
mirDat %>% dplyr::select(type:month, bcxJSD = bcxJSDOmni, medJSD = medJSDOmni) %>%
group_by(type, antigen, month) %>%
summarize(bcxJSD = mean(bcxJSD), medJSD = mean(medJSD)) %>%
gather(metric, P, bcxJSD, medJSD) -> mirOmni
mirOmni
NTaxaAtLevel %>% bind_rows(specRow[,1:2]) %>% unite(nLev, taxLevels, ntaxa, remove = FALSE) -> NTaxaAtLevel2
NTaxaAtLevel2
bind_rows(
    permKernTable %>% mutate(metric = 'med'),
    permKernTableGaus %>% mutate(metric = 'bcx')
    ) %>%
dplyr::select(type, antigen, month, metric, mir.P) %>%
pass -> WufPData
fixant <- function(df){
    df %>%
    mutate(antigen = stringr::str_replace_all(antigen, "\\.", " ")) %>%
    mutate(antigen = stringr::str_replace_all(antigen, "_", " ")) %>%
    #mutate(metric =  stringr::str_replace_all(metric, "bcx", "")) %>%
    pass
}

fixstuff <- function(df){
    df %>%
    fixant %>%
    mutate(metric =  stringr::str_replace_all(metric, "JSD", "")) %>%
    pass
}
mirDat %>% fixant %>% head
mirDat %>% 
#mutate(antigen = stringr::str_replace_all(antigen, "\\.", " ")) %>% 
fixstuff %>%
ggplot(aes(x = ntaxa, y = P, col = factor(month), fill = factor(month))) +
geom_point(pch = 21) +
facet_grid(type + antigen ~ metric, labeller = labeller(antigen = label_wrap_gen(width = 10))) + 
scale_x_log10(breaks = c(3, NTaxaAtLevel2$ntaxa, 1000), labels = c("omni", NTaxaAtLevel2$nLev, "wunifrac")) +
scale_y_log10(breaks = c(0.002, 0.01, 0.05, 0.2, 1)) + 
geom_hline(yintercept=0.05, col = 'blue', alpha = 0.5) + geom_hline(yintercept=0.01, col = 'red', alpha = 0.5) +
#geom_hline(data = mirOmni, aes(yintercept = P, col = factor(month))) +
#annotation_logticks(sides = 'bl') +
#geom_rug(data = mirOmni, aes(y = P, col = factor(month)), inherit.aes = F) +
geom_point(data = mirOmni %>% ungroup %>% fixstuff,
           aes(x = 3, y = P, col = factor(month), fill = factor(month)), inherit.aes = F, pch = 22, size = 2) +
geom_point(data = WufPData %>% ungroup %>% fixant,
           aes(x = 1000, y = mir.P, col = factor(month), fill = factor(month)), inherit.aes = F, pch = 24, size = 2) +

scale_colour_manual(values=cbPalette) + 
scale_fill_manual(values=alpha(cbPalette, 0.5)) + 
theme_bw() +
theme(axis.text.x = element_text(angle = 90, hjust = 1,, vjust = 0.5, size = 10),
     strip.text.y = (element_text(angle = 90))) -> pjsd0

options(repr.plot.width=8, repr.plot.height= 6)
pjsd0

options(par0)
# I'd like to add weighted unifrac as a tick mark on the right.
NTaxaAtLevel2
# New combined data frame that has omnibus, regular, and wunifrac all in one
bind_rows(
     mirDat %>% mutate(metric =  stringr::str_replace_all(metric, "JSD", "")) %>%
    mutate(test = "JSD"),
     mirOmni %>% ungroup %>% mutate(metric =  stringr::str_replace_all(metric, "JSD", "")) %>%
    mutate(taxLevels = "Omnibus") %>% mutate(test = "Omnibus"),
     WufPData %>% ungroup %>% dplyr::rename(P = mir.P) %>%
    mutate(taxLevels = "WUnifrac") %>% mutate(test = "WUnifrac")
) %>% 
mutate(antigen = factor(antigen, levels = c(ants2, ants1, "ANY.ENV.PTEG"))) %>%
mutate(type = factor(type, levels = c("IgA", "IgG", "CD4+"))) %>%
mutate(taxLevels = factor(taxLevels, levels = c("Omnibus", NTaxaAtLevel2$taxLevels, "WUnifrac"))) %>%
dplyr::select(-c(bcxJSDOmni:medJSDOmni))%>%
unite(nLev, taxLevels, ntaxa, remove = FALSE) %>%
mutate(nLev = stringr::str_replace(nLev, "_NA", "")) %>%
#mutate(antigen = stringr::str_replace_all(antigen, "\\.", " ")) %>%
#mutate(antigen = stringr::str_replace_all(antigen, "_", " ")) %>%
 mutate(antigen = factor(antigen, labels = stringr::str_replace_all(levels(antigen), "\\.", " "))) %>%
 mutate(antigen = factor(antigen, labels = stringr::str_replace_all(levels(antigen), "_", " "))) %>%


# mutate(antigen = factor(antigen, labels = (levels(antigen)))) %>%
# mutate(antigen = factor(antigen, labels = (levels(antigen)))) %>%

mutate(test = factor(test, levels = c('Omnibus', 'JSD', 'WUnifrac'))) %>%
pass -> mirDat2
binding factor and character vector, coercing into character vectorbinding character and factor vector, coercing into character vector
mirDat2 %>% filter(type == 'CD4+')
mirDat2 %>% head
mirDat2 %>%
filter(type == "IgG") %>%
ggplot(aes(x = taxLevels, y = P, col = factor(month), fill = factor(month), shape = test)) +
geom_point(size = 2) +
facet_grid(type + antigen ~ metric, labeller = labeller(antigen = label_wrap_gen(width = 10))) + 
#scale_x_log10(breaks = c(3, NTaxaAtLevel2$ntaxa, 1000), labels = c("omni", NTaxaAtLevel2$nLev, "wunifrac")) +
scale_y_log10(breaks = c(0.002, 0.01, 0.05, 0.2, 1)) + 
geom_hline(yintercept=0.05, col = 'blue', alpha = 0.5) + geom_hline(yintercept=0.01, col = 'red', alpha = 0.5) +

scale_shape_manual(values = c(22, 21, 24)) +
scale_colour_manual(values=cbPalette) + 
scale_fill_manual(values=alpha(cbPalette, 0.5)) + 
theme_bw() +
theme(axis.text.x = element_text(angle = 90, hjust = 1,, vjust = 0.5, size = 10),
     strip.text.y = (element_text(angle = 90))) -> pjsd


options(repr.plot.width=6, repr.plot.height= 8)
pjsd

ggsave('figures/KernelPVsLevel.png', width = 6, height = 8)

options(par0)

X-axis is now spaced evenly

Table SX. P values of kernel regression tests. Circles indicate jensen shannon values at different taxonomic resolutions. Squares are the omnibus p-value for that cohort of tests. Triangles indicate kernel regression p-values for the corresponding weighted unifrac test.

The blue and red lines indicate p values of 5% and 1% respectively.

Observations: The weighted unifrac test is sensitive. In cases where only one taxonic level hits, weighted unifrac often also falls at some statistically significant value. The omnibus p value is often higher than the weighted unifrac one. Weighted unifrac seems like a good test for identifying patterns at any level that relate to an outcome. The jensen shannon informs us about which level the pattern is observed.

Local Tests

Family, genera and species vs wuf1

I might even be able to drill down to every level.

model_each_species <- function(ps, f, pthresh = 1, q = FALSE){
    # Start with the otu table
ps %>%
# reshape it so we have clr values for every taxon-sample pair
otu_table %>% as.data.frame %>% rownames_to_column("Sample") %>% gather(Taxon, clr, -Sample) %>%
    # bind that to the sample data
    # doing this here seems remarkably inefficient, but its not creating a bottleneck so I'll leave it.
left_join(
    ps %>%
    # the sample data need to have MDS1 and MDS2 appended to them
    phylo_join(
    psN2.pcoa %>% scores(display = "sites") %>% # hardcoded psN2.pcoa
        as.data.frame %>% 
        rownames_to_column %>% 
        dplyr::select('rowname', 'MDS1', 'MDS2'),
    by = 'rowname'
) %>%
    # back to binding to sample data
    sample_data %>% as('data.frame') %>% rownames_to_column("Sample"),
     by = 'Sample') %>%

group_by(Taxon) %>%  # group and nest for model run
nest %>%
mutate(Mod = map(data, f)) %>% # apply model over each species
mutate(Glance = map(Mod, glance), Tidy = map(Mod, tidy)) %>% # extract relevant data from model
# view model
dplyr::select(Taxon, Tidy) %>% unnest %>%
mutate(term = gsub('[\\( \\)]','', term)) %>% # remove parentheses from "(Intercept)"
gather(meas, val, estimate:p.value) %>% 
unite(meas, term, meas) %>% spread(meas, val) %>% arrange(clr_estimate) %>% 
dplyr::select(Taxon, Intercept_estimate, clr_estimate, clr_std.error, clr_p.value) %>%
    # add q value
    {if(q) mutate(., clr_q.value = p2q(clr_p.value)) else .} %>%
    
 filter(clr_p.value < pthresh) %>%

     #Join taxonomy information
     left_join(
     as.data.frame(ps@tax_table@.Data) %>% as.tibble %>% dplyr::select(Kingdom:Genus, Species, tag) %>%
         mutate(tag = as.character(tag)), # mutate so tag is and taxon are both character class
     by = c("Taxon" = "tag")) %>%
pass
 }
model_each_species_for_antigen <- function(antigen, ps = psN2){
    ps %>%
    model_each_species(function(df){glm(medcode2(get(antigen)) ~ clr, data = df, family = 'binomial')}, q = TRUE, pthresh = 1)
}
ColsToRun <- c('IgG_Con.6.gp120.B_Month_6.5', 'IgG_Con.6.gp120.B_Month_12', 'IgG_gp41_Month_0', 'IgG_gp41_Month_6.5', 'IgG_gp70_B.CaseA_V1_V2_Month_12', 'IgG_ZM96.gp140_Month_6.5', 'MDS1', 'isMale' ) 
model_each_species_case <- function(ps){
    
    ps %>% model_each_species(function(df){glm(MDS1 ~ clr, data = df, family = 'gaussian')}, q = TRUE, pthresh = 1) %>%
arrange(clr_estimate) %>%
mutate(Taxon = factor(Taxon, levels = Taxon[order(clr_estimate)])) %>%
    mutate(test = 'gaussian', antigen = 'MDS1') %>%
pass -> loc_mds1Glms
    
        ps %>% model_each_species(function(df){glm(log10(IgG_Con.6.gp120.B_Month_12 + 100) ~ clr, data = df, family = 'gaussian')}, q = TRUE, pthresh = 1) %>%
arrange(clr_estimate) %>%
 mutate(Taxon = factor(Taxon, levels = levels(loc_mds1Glms$Taxon))) %>%
    mutate(test = 'gaussian', antigen = 'Con.6.gp120.B_Month_12') %>%
 pass -> loc_gp120Glms
    
      tibble(antigen = ColsToRun) %>% mutate(model = map(antigen, ~model_each_species_for_antigen(., ps = ps))) %>%
  unnest %>% mutate(Taxon = factor(Taxon, levels = levels(loc_mds1Glms$Taxon))) %>%
        mutate(test = 'binomial') %>%
    pass-> loc_logitCoefs
    

    #list(loc_mds1Glms, loc_gp120Glms, loc_logitCoefs)
     bind_rows(loc_mds1Glms, loc_gp120Glms, loc_logitCoefs) %>% dplyr::select(test, antigen, everything())
    
}
#psDf %>% mutate(ps2 = map(ps, ~swap.phyloseq.taxnames(tag_phyloseq(remove_tag_phyloseq(.))))) -> test
#psDf[[1,"clr"]] %>% tax_table
psDf[[1]]
[1] "Phylum"  "Class"   "Order"   "Family"  "Genus"   "Species"
psDf %>% print
ptm = proc.time()
psDf %>% dplyr::select(taxLevels, ntaxa, clr) %>% mutate(localmod = map(clr, model_each_species_case)) ->psDfLoc
glm.fit: fitted probabilities numerically 0 or 1 occurredglm.fit: fitted probabilities numerically 0 or 1 occurredglm.fit: fitted probabilities numerically 0 or 1 occurredglm.fit: fitted probabilities numerically 0 or 1 occurredglm.fit: fitted probabilities numerically 0 or 1 occurredglm.fit: fitted probabilities numerically 0 or 1 occurredglm.fit: fitted probabilities numerically 0 or 1 occurredglm.fit: fitted probabilities numerically 0 or 1 occurredglm.fit: fitted probabilities numerically 0 or 1 occurredglm.fit: fitted probabilities numerically 0 or 1 occurredglm.fit: fitted probabilities numerically 0 or 1 occurredglm.fit: fitted probabilities numerically 0 or 1 occurred
proc.time() - ptm
   user  system elapsed 
 42.166   0.148  42.246 
print(psDfLoc)
psDfLoc %>% dplyr::select(-clr) %>% unnest(localmod) -> tmp
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorUnequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorUnequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorUnequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorUnequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorUnequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorUnequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
psDfLoc$taxLevels
[1] "Phylum"  "Class"   "Order"   "Family"  "Genus"   "Species"
tmp %>% mutate(taxLevels = factor(taxLevels, levels = psDfLoc$taxLevels)) -> LocalTests
LocalTests %>% 
filter(antigen != "MDS1") %>%
write_csv("tables/AllLocalTests.csv")

I want to show the local tests vs antibodies.

LocalTests %>% head
options(repr.plot.width=6, repr.plot.height=8)
LocalTests %>% 
filter(antigen != "MDS1") %>%
#Clean up labels
mutate(antigen = stringr::str_replace_all(antigen, "_", " ")) %>%
mutate(antigen = stringr::str_replace_all(antigen, " Month", " -- Month")) %>%
mutate(antigen = stringr::str_replace_all(antigen, " IgG ", "")) %>%
ggplot(aes(x = factor(taxLevels), y = clr_q.value)) + geom_point(size = 0.1) +
geom_hline(yintercept = 0.2, color = 'grey') +
 geom_hline(yintercept = 0.05, color = 'blue') + geom_hline(yintercept = 0.01, color = 'green') +
facet_wrap(~antigen + test) + scale_y_log10() +
theme_bw() +
theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5))

ggsave('figures/LocalQEveryLevel.png')
Saving 7.29 x 4.5 in image

options(repr.plot.width=6, repr.plot.height=8)
LocalTests %>% 
filter(antigen != "MDS1") %>%
#Clean up labels
mutate(antigen = stringr::str_replace_all(antigen, "_", " ")) %>%
mutate(antigen = stringr::str_replace_all(antigen, " Month", " -- Month")) %>%
mutate(antigen = stringr::str_replace_all(antigen, " IgG ", "")) %>%
ggplot(aes(x = factor(taxLevels), y = clr_p.value)) + geom_point(size = 0.1) +
geom_hline(yintercept = 0.2, color = 'grey') +
 geom_hline(yintercept = 0.05, color = 'blue') + geom_hline(yintercept = 0.01, color = 'green') +
facet_wrap(~antigen + test) +# scale_y_log10() +
theme_bw() +
theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5))
ggsave('figures/LocalPEveryLevel.png')
Saving 7.29 x 4.5 in image

options(repr.plot.width=6, repr.plot.height=8)
LocalTests %>% 
filter(antigen != "MDS1") %>%
#Clean up labels
mutate(antigen = stringr::str_replace_all(antigen, "_", " ")) %>%
mutate(antigen = stringr::str_replace_all(antigen, " Month", " -- Month")) %>%
mutate(antigen = stringr::str_replace_all(antigen, " IgG ", "")) %>%
ggplot(aes(x = factor(taxLevels), y = clr_p.value)) + geom_point(size = 0.1) +
geom_hline(yintercept = 0.2, color = 'grey') +
 geom_hline(yintercept = 0.05, color = 'blue') + geom_hline(yintercept = 0.01, color = 'green') +
facet_wrap(~antigen + test) + scale_y_log10() +
theme_bw() +
theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5))
ggsave('figures/LocalPEveryLevel_LogScale.png')
Saving 7.29 x 4.5 in image

order_taxa_by_mds1 <- function(df){
    # this has to be a model_each_species type of data frame
    df %>% filter(antigen == 'MDS1' & test == 'gaussian') %>%
    mutate(TaxonF = factor(Taxon, levels = Taxon[order(clr_estimate)])) -> mds1df
    df %>% mutate(TaxonF = factor(Taxon, levels = levels(mds1df$TaxonF)))
}

To my annoyance, everything is labeled with IgG except gp120_12

LocalTests %>%
filter(taxLevels == 'Family') %>%
order_taxa_by_mds1 %>%

filter(
    (antigen %in% c('IgG_gp41_Month_0', 'IgG_gp41_Month_6.5', 'IgG_Con.6.gp120.B_Month_6.5'))|
    (antigen == 'Con.6.gp120.B_Month_12' & test == 'gaussian')
      ) %>%
mutate(antigen = factor(antigen,
                        levels = c('IgG_gp41_Month_0', 'IgG_gp41_Month_6.5',
                                   'IgG_Con.6.gp120.B_Month_6.5', 'Con.6.gp120.B_Month_12'))) %>%
filter(clr_p.value < 0.05 & clr_q.value < 0.2) %>%
dplyr::select(antigen:clr_estimate) %>%
dplyr::select(-Intercept_estimate) %>%
mutate(cordir = sign(clr_estimate)) %>%
pass
LocalTests %>% pull(antigen) %>% unique
[1] "MDS1"                            "Con.6.gp120.B_Month_12"          "IgG_Con.6.gp120.B_Month_6.5"    
[4] "IgG_Con.6.gp120.B_Month_12"      "IgG_gp41_Month_0"                "IgG_gp41_Month_6.5"             
[7] "IgG_gp70_B.CaseA_V1_V2_Month_12" "IgG_ZM96.gp140_Month_6.5"        "isMale"                         
# Family Hits
LocalTests %>%
filter(taxLevels == 'Family') %>%
order_taxa_by_mds1 %>%

filter(
    (antigen %in% c('IgG_gp41_Month_0', 'IgG_gp41_Month_6.5', 'IgG_Con.6.gp120.B_Month_6.5', 
                    'IgG_ZM96.gp140_Month_6.5','IgG_gp70_B.CaseA_V1_V2_Month_12'))|
    (antigen == 'Con.6.gp120.B_Month_12' & test == 'gaussian')
      ) %>%
mutate(antigen = factor(antigen, levels = c(
    'IgG_gp41_Month_0', 'IgG_gp41_Month_6.5', 'IgG_Con.6.gp120.B_Month_6.5', 'Con.6.gp120.B_Month_12',
    'IgG_ZM96.gp140_Month_6.5','IgG_gp70_B.CaseA_V1_V2_Month_12'
))) %>%

pass -> tmp

#tmp$antigen %>% unique

tmp %>% filter(clr_p.value < 0.05 & clr_q.value < 0.2) %>%
pull(Taxon) %>% unique -> useFamily

tmp %>% filter(Taxon %in% useFamily) %>%

#Clean up labels
mutate(antigen = stringr::str_replace_all(antigen, "_", " ")) %>%
mutate(antigen = stringr::str_replace_all(antigen, " Month", " -- Month")) %>%
mutate(antigen = stringr::str_replace_all(antigen, "IgG ", "")) %>%

ggplot(aes(x = TaxonF, y = clr_estimate,
           color = (clr_p.value < 0.05), shape =(clr_q.value < 0.2))) +
geom_point(size = 3) + 
geom_errorbar(aes(ymin = clr_estimate - 2*clr_std.error, ymax = clr_estimate + 2*clr_std.error)) + 
theme_bw() +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) + geom_hline(yintercept = 0) +
facet_wrap(~antigen, ncol = 1, scales = 'free_y') + xlab("Family Level Taxon")
# Show the censored ones accross - so this would be everything with at least one hit
# but also show what they are in all cases.

ggsave('figures/anyFamilyIgg.png', width = 6, height = 8)

I think its worth digging into clostridia and Prophyromonidaceae with stacked bars

Proportionality heatmap

Family level

Lets come back to this after we’ve done the local tests. Since we need them to color code the axes.

psDf %>% print
psDf %>% filter(taxLevels == 'Family') %>% dplyr::select(ps) %>% pull %>%.[[1]]
phyloseq-class experiment-level object
otu_table()   OTU Table:         [ 39 taxa and 21 samples ]
sample_data() Sample Data:       [ 21 samples by 35 sample variables ]
tax_table()   Taxonomy Table:    [ 39 taxa by 12 taxonomic ranks ]
phy_tree()    Phylogenetic Tree: [ 39 tips and 38 internal nodes ]
print(psDf)
#nFamilyTaxa <- NTaxaAtLevel %>% filter(taxLevels == 'Family') %>% pull(ntaxa)

psDf %>% filter(taxLevels == 'Family') %>% dplyr::select(psNoZero) %>% pull %>%.[[1]] %>%
otu_table %>% as.data.frame %>%
pass -> myRel

ptm = proc.time()
phiBoot <- boot(data = myRel, statistic = boot_phi, R = 1000)
proc.time() - ptm
   user  system elapsed 
  5.911   0.052   5.962 
ptm = proc.time()
tidyCI <- unwarn(
    tidy(phiBoot,conf.int=TRUE,conf.method="bca")
    )
proc.time() - ptm
   user  system elapsed 
 11.060   0.132  11.159 
myRel %>% make_proportionality_matrix %>% 
         as.data.frame %>%
         rownames_to_column("TaxonX") %>% gather(TaxonY, phi, -TaxonX) %>%
    filter(TaxonX != TaxonY) %>% data.frame(tidyCI) -> namedTidyCI
head(LocalTests)
LocalTests %>% filter(test == 'gaussian' &
                        antigen == 'MDS1' &
                         clr_p.value <0.05 &
                        clr_q.value < 0.2&
                        taxLevels == "Family") -> tmp
tmp %>% pull(Taxon) -> MDS1Fam
tmp %>% filter(clr_estimate < 0) %>% pull(Taxon) -> lowMDS1Fam
tmp %>% filter(clr_estimate >= 0) %>% pull(Taxon) -> highMDS1Fam

https://stackoverflow.com/questions/48531987/incorporate-more-information-about-variables-on-axes-into-a-heatmap-in-ggplot/48532983#48532983

I’d like to do this, but for gp41 baseline and gp120 as well.

useFamily
 [1] "Bacteroidetes.4"      "Firmicutes.4"         "Firmicutes.2"         "Clostridia"           "Bacteroides"         
 [6] "Firmicutes.5"         "Porphyromonadaceae.1" "Porphyromonadaceae"   "Bacteroidetes.1"      "Anaerococcus"        
LocalTests %>% head
reshape2::melt
function (data, ..., na.rm = FALSE, value.name = "value") 
{
    UseMethod("melt", data)
}
<bytecode: 0x562cc04c4148>
<environment: namespace:reshape2>
targStat <- "phi"
namedTidyCI %>% dplyr::select(TaxonX, TaxonY, targStat) %>% spread(key = TaxonY, value = targStat) %>%
remove_rownames %>% column_to_rownames("TaxonX") %>%
as.dist %>% as.matrix -> phidata

phi_dd <- as.dist(phidata)
phi_hc <- hclust(phi_dd)

phidata %>%
#.[phi_hc$order, phi_hc$order] %>% # this way also worked just fine
reshape2::melt() %>%
mutate(Var1 = factor(Var1, levels = unique(phi_hc$labels)[phi_hc$order])) %>%
mutate(Var2 = factor(Var2, levels = unique(phi_hc$labels)[phi_hc$order])) %>%
pass-> tmp

p_phi_1 <- ggplot(tmp, aes(Var1, Var2, fill =(value))) + 
geom_tile() +
  scale_fill_gradient(high = "grey90", low = "red", 
    space = "Lab", 
    name="phi",
                    limits = c(NA, 3), na.value = "white") +
#  theme_minimal()+ # minimal theme
 theme(axis.text.x = element_text(
     angle = 90, vjust = 1, size = 7, hjust = 1,
     face = ifelse(levels(tmp$Var1) %in% useFamily, "bold", "plain"),
     colour = ifelse(levels(tmp$Var1) %in% useFamily, "black", "grey30")
                                 ),
       axis.text.y = element_text(
           size = 7,
           face = ifelse(levels(tmp$Var1) %in% MDS1Fam, "bold", "plain"),
           colour = ifelse(levels(tmp$Var1) %in% lowMDS1Fam, "red",
                          ifelse(levels(tmp$Var1) %in% highMDS1Fam, "blue", "grey30"))
       ))+
 coord_fixed() +
labs(x = "Family Any Igg",y = "Family MDS1 (red-low, blue-high)" ) +
# rectangles around the three clusters, positioned by eye
  geom_rect(aes(xmin = 0 + 0.5, xmax = 10 - 0.5, ymin = 0 + 0.5, ymax = 10 - 0.5),
               fill = "transparent", color = "gray20", size = 1.5) +
  geom_rect(aes(xmin = 13 + 0.5, xmax = 24 - 0.5, ymin = 13 + 0.5, ymax = 24 - 0.5),
               fill = "transparent", color = "gray20", size = 1.5) +
  geom_rect(aes(xmin = 23 + 0.5, xmax = 37 - 0.5, ymin = 23 + 0.5, ymax = 37 - 0.5),
               fill = "transparent", color = "gray20", size = 1.5)


p_phi_1

# ggsave("figures/phi_vs_mds1_and_igg.png", p_phi_1, width = 6, height = 6)
LocalTests %>%
filter(taxLevels == 'Family') %>%
order_taxa_by_mds1 %>%

filter(
    (antigen %in% c('IgG_gp41_Month_0', 'IgG_gp41_Month_6.5', 'IgG_Con.6.gp120.B_Month_6.5',
                   'IgG_ZM96.gp140_Month_6.5','IgG_gp70_B.CaseA_V1_V2_Month_12'))|
    (antigen == 'Con.6.gp120.B_Month_12' & test == 'gaussian')
      ) %>%
mutate(antigen = factor(antigen, levels = c('IgG_gp41_Month_0', 'IgG_gp41_Month_6.5', 'IgG_Con.6.gp120.B_Month_6.5', 'Con.6.gp120.B_Month_12',
                                           'IgG_ZM96.gp140_Month_6.5','IgG_gp70_B.CaseA_V1_V2_Month_12'))) %>%
pass -> tmp

tmp %>% dplyr::select(antigen, Taxon, clr_estimate, clr_p.value, clr_q.value) %>%
mutate(clr_sign = sign(clr_estimate)) %>%
mutate(isHit = ifelse(clr_p.value < 0.05 & clr_q.value < 0.2, 1, 0)) %>%
mutate(Taxon = factor(Taxon, levels = unique(phi_hc$labels)[phi_hc$order])) %>%
pass -> chorddata
targStat <- "conf.low"
namedTidyCI %>% dplyr::select(TaxonX, TaxonY, targStat) %>% spread(key = TaxonY, value = targStat) %>%
remove_rownames %>% column_to_rownames("TaxonX") %>%
as.dist %>% as.matrix -> phidata

phi_dd <- as.dist(phidata)
phi_hc <- hclust(phi_dd)

phidata %>%
#.[phi_hc$order, phi_hc$order] %>% # this way also worked just fine
reshape2::melt() %>%
mutate(Var1 = factor(Var1, levels = unique(phi_hc$labels)[phi_hc$order])) %>%
mutate(Var2 = factor(Var2, levels = unique(phi_hc$labels)[phi_hc$order])) %>%
pass-> tmp

ggplot(tmp, aes(Var1, Var2, fill =(value))) + 
geom_tile() +
  scale_fill_gradient(high = "grey90", low = "red", 
    space = "Lab", 
    name="phi",
                    limits = c(NA, 3), na.value = "white") +
#  theme_minimal()+ # minimal theme
 theme(axis.text.x = element_text(
     angle = 90, vjust = 1, size = 7, hjust = 1,
     face = ifelse(levels(tmp$Var1) %in% useFamily, "bold", "plain"),
     colour = ifelse(levels(tmp$Var1) %in% useFamily, "black", "grey30")
                                 ),
       axis.text.y = element_text(
           size = 7,
           face = ifelse(levels(tmp$Var1) %in% MDS1Fam, "bold", "plain"),
           colour = ifelse(levels(tmp$Var1) %in% lowMDS1Fam, "red",
                          ifelse(levels(tmp$Var1) %in% highMDS1Fam, "blue", "grey30"))
       ))+
 coord_fixed() +
labs(x = "Family Any Igg",y = "Family MDS1 (red-low, blue-high)" )-> p_phi_low
p_phi_low


# ggsave("figures/phi_vs_mds1_and_igg.png", p_phi_1, width = 6, height = 6)
targStat <- "conf.high"
namedTidyCI %>% dplyr::select(TaxonX, TaxonY, targStat) %>% spread(key = TaxonY, value = targStat) %>%
remove_rownames %>% column_to_rownames("TaxonX") %>%
as.dist %>% as.matrix -> phidata

phi_dd <- as.dist(phidata)
phi_hc <- hclust(phi_dd)

phidata %>%
#.[phi_hc$order, phi_hc$order] %>% # this way also worked just fine
reshape2::melt() %>%
mutate(Var1 = factor(Var1, levels = unique(phi_hc$labels)[phi_hc$order])) %>%
mutate(Var2 = factor(Var2, levels = unique(phi_hc$labels)[phi_hc$order])) %>%
pass-> tmp

ggplot(tmp, aes(Var1, Var2, fill =(value))) + 
geom_tile() +
  scale_fill_gradient(high = "grey90", low = "red", 
    space = "Lab", 
    name="phi",
                    limits = c(NA, 3), na.value = "white") +
#  theme_minimal()+ # minimal theme
 theme(axis.text.x = element_text(
     angle = 90, vjust = 1, size = 7, hjust = 1,
     face = ifelse(levels(tmp$Var1) %in% useFamily, "bold", "plain"),
     colour = ifelse(levels(tmp$Var1) %in% useFamily, "black", "grey30")
                                 ),
       axis.text.y = element_text(
           size = 7,
           face = ifelse(levels(tmp$Var1) %in% MDS1Fam, "bold", "plain"),
           colour = ifelse(levels(tmp$Var1) %in% lowMDS1Fam, "red",
                          ifelse(levels(tmp$Var1) %in% highMDS1Fam, "blue", "grey30"))
       ))+
 coord_fixed() +
labs(x = "Family Any Igg",y = "Family MDS1 (red-low, blue-high)" )-> p_phi_high
p_phi_high

# ggsave("figures/phi_vs_mds1_and_igg.png", p_phi_1, width = 6, height = 6)
chorddata %>%
#Clean up labels
mutate(antigen = stringr::str_replace_all(antigen, "_", " ")) %>%
mutate(antigen = stringr::str_replace_all(antigen, " Month", " -- Month")) %>%
mutate(antigen = stringr::str_replace_all(antigen, "IgG ", "")) %>%

ggplot(
    aes(x = Taxon, y = antigen, alpha = factor(isHit), color = factor(clr_sign))) +
scale_alpha_discrete(range = c(0, 1)) +
guides(alpha = FALSE) +
theme_minimal() +
     coord_fixed(ratio = 2) +
scale_colour_manual(values = c("red", "blue")) +
 theme(axis.text.x = element_text(
     angle = 90, vjust = 0.5, size = 7, hjust = 1,
     face = ifelse(levels(chorddata$Taxon) %in% useFamily, "bold", "plain"),
     colour = ifelse(levels(chorddata$Taxon) %in% useFamily, "black", "grey30")),
     plot.margin = unit(c(0,3,1,3), "cm")
     ) +
#guides(col = TRUE) +
guides(color=guide_legend(title="Sign of GLM")) +
labs(x = "Family",y = "Antigen -- Month" ) +
geom_point() -> guitar_chords
Using alpha for a discrete variable is not advised.
par <- options()
options(repr.plot.width=10, repr.plot.height= 5)
guitar_chords

options(par)
p_phi_1a <- p_phi_1 + 
theme(axis.text.x = element_blank(),
     axis.title.x = element_blank(),
     plot.margin = unit(c(1, 3, -5.5, 4), "cm"))

par <- options()
options(repr.plot.width=8, repr.plot.height= 8)

p_phi_cord <- cowplot::plot_grid(p_phi_1a, guitar_chords, nrow = 2, align = "v")

p_phi_cord

#phi_legend <- cowplot::get_legend(p_phi_1)
# cowplot::ggdraw(
#     cowplot::plot_grid(
#     cowplot::plot_grid(p_phi_1a, guitar_chords, ncol = 1, align = "v"),
#       cowplot::plot_grid(phi_legend, NULL, ncol = 1),
#       rel_widths = c(10,1)
#         ))

 ggsave('figures/phi_heatmap_withlegend.png', width = 10, height = 10)


options(par)

Stacked bars

# More color-blind friendly colorbalettes
#http://colorbrewer2.org/#type=qualitative&scheme=Paired&n=10
cb10 <- c('#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a')

cb12 <- c('#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99','#b15928')

# Less color-blind friendly, but still nice.
#https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/
trub20 <- c('#e6194b','#3cb44b','#ffe119','#0082c8','#f58231','#911eb4','#46f0f0','#f032e6','#d2f53c','#fabebe','#008080','#e6beff','#aa6e28','#fffac8','#800000','#aaffc3','#808000','#ffd8b1','#000080','#808080','#FFFFFF','#000000')
options(repr.plot.width=8, repr.plot.height= 4)

Bookmark Here. Stuck for strange reasons.

ordered_pub_id_df <- psN2 %>% sample_data %>% dplyr::select(pub_id, rMDS1, newname, MDS1) %>% arrange(rMDS1) %>% mutate(MDS1 = round(MDS1, 2), fig3tick = paste(pub_id, MDS1,sep = "_"))
Setting class(x) to multiple strings ("tbl_df", "tbl", ...); result will no longer be an S4 object
ordered_pub_id_df
fig3tick <- ordered_pub_id_df %>% pull(fig3tick)
p_phy <- plot_bar(psN2, x = 'newname', fill = 'Phylum') + scale_fill_manual(values = cb10)  + xlab("") +
ggtitle("All Phyla")+ theme_bw() +
theme(axis.text.x = element_text(angle = 90, hjust = 1,, vjust = 0.5, size = 10),
     strip.text.y = (element_text(angle = 90))) + scale_x_discrete(labels = fig3tick) + labs(x = "pub_id _ MDS1", y = "Relative Abundance")
p_phy

#ggsave('plots/Phyla_by_wuf1.png')
p_firm <-  subset_taxa(psN2, Phylum == 'Firmicutes') %>%
plot_bar( x = 'newname', fill = 'Order') +
theme_bw() +
theme(axis.text.x = element_text(angle = 90, hjust = 1,, vjust = 0.5, size = 10),
     strip.text.y = (element_text(angle = 90))) +
scale_fill_manual(values = cb10) + scale_x_discrete(labels = fig3tick) + labs(x = "pub_id _ MDS1", y = "Relative Abundance")
p_firm

#ggsave('plots/MostFirmicutesAreClostridiales.png')
p_clostridia <-  subset_taxa(psN2, Class == 'Clostridia') %>%
plot_bar( x = 'newname', fill = 'Family') + scale_fill_manual(values = cb10)  + xlab("") +
ggtitle("Families of Order Clostridiales (All Class Clostridia)")+ theme_bw() +
theme(axis.text.x = element_text(angle = 90, hjust = 1,, vjust = 0.5, size = 10),
     strip.text.y = (element_text(angle = 90))) + scale_x_discrete(labels = fig3tick) + labs(x = "pub_id _ MDS1", y = "Relative Abundance")
p_clostridia

# p_porph <-  subset_taxa(psN2, Family == 'Porphyromonadaceae') %>%
# plot_bar( x = 'newname', fill = 'Genus') + scale_fill_manual(values = cb10) #+ theme_bw()
# p_porph
# p_bact <- subset_taxa(psN2, Phylum == 'Bacteroidetes') %>% # all class (Bacteroidia), order (Bacteroidales)
# plot_bar(x = 'newname', fill = 'Family') + scale_fill_manual(values = cb10) #+ theme_bw()
# p

# ggsave('figures/Bacteroidetes_Families.png')
p_ClosXI <- subset_taxa(psN2, Family == 'Clostridiales_Incertae_Sedis_XI') %>%
plot_bar( x = 'newname', fill = 'Genus') + scale_fill_manual(values = cb10) + xlab("") +
ggtitle("Genera of Family Clostridiales_Incertae_Sedis_XI")+ theme_bw() +
theme(axis.text.x = element_text(angle = 90, hjust = 1,, vjust = 0.5, size = 10),
     strip.text.y = (element_text(angle = 90))) + scale_x_discrete(labels = fig3tick) + labs(x = "pub_id _ MDS1", y = "Relative Abundance")
p_ClosXI

#ggsave('figures/Clostridiales_Incertae_Sedis_XI_Genus.png')

p_Bact <- subset_taxa(psN2, Phylum == 'Bacteroidetes') %>%
plot_bar( x = 'newname', fill = 'Family') + scale_fill_manual(values = cb10) + xlab("") +
ggtitle("Families of Class Bacteroidetes (All Order Bacteroidiales)")+ theme_bw() +
theme(axis.text.x = element_text(angle = 90, hjust = 1,, vjust = 0.5, size = 10),
     strip.text.y = (element_text(angle = 90))) + scale_x_discrete(labels = fig3tick) + labs(x = "pub_id _ MDS1", y = "Relative Abundance")
p_Bact

#ggsave('figures/Bacteroides.png')
options(repr.plot.width=14, repr.plot.height= 8)
sb <- cowplot::plot_grid(p_phy, p_Bact,p_clostridia,p_ClosXI,ncol = 2, labels = c("A", "B", "C", "D"))
sb
cowplot::save_plot('figures/stacked_bars.png', sb, base_width = 14, base_height = 8)
cowplot::save_plot('figures/stacked_bars.svg', sb, base_width = 14, base_height = 8)

Exporting OTU tables and Taxa tables at each agglomeration level

# psDf %>%
# mutate(OTU = map(ps, ~data.frame(otu_table(.)))) %>%
# mutate(Tax = map(ps, ~data.frame(tax_table(.)))) %>%
# mutate(OTUCount = map (psCount, ~data.frame(otu_table(.)))) %>%
# pass -> psDf1
psDf %>%
mutate(OTU = map(ps, ~data.frame(otu_table(.)))) %>%
mutate(Tax = map(ps, ~as.data.frame(.@tax_table@.Data))) %>%
mutate(OTUCount = map (psCount, ~data.frame(otu_table(.)))) %>%
pass -> psDf1
psDf1 %>%
.[1:5,] %>%
mutate(Tax = map(Tax, ~dplyr::select(.,-oldname2))) %>%
print
# https://stackoverflow.com/questions/50341012/return-the-mapped-object-if-expression-inside-of-purrrpossibly-fails/50341205#50341205
rm_oldname2 <- function(x){
    f = possibly(function() dplyr::select(x, -oldname2), otherwise = x)
        f()
}
psDf1 %>%
#.[1:5,] %>%
mutate(Tax = map(Tax, rm_oldname2)) %>%
pass -> psDf1b
print(psDf1)
# Show which species level OTUs are contained in each agglomerated group:
psDf1b %>%
.[1:5,] %>%
mutate(TaxIdx = map(Tax, function(df){
    df %>%
    mutate(tag = as.character(tag), oldGroups = as.character(oldGroups)) %>%
    dplyr::select(tag, oldGroups) %>%
    mutate(oldGroups = strsplit(oldGroups, ",")) %>%
    unnest(oldGroups)
})) %>%
dplyr::select(taxLevels, TaxIdx) %>%
unnest(TaxIdx) %>%
mutate(oldGroups = trimws(oldGroups)) %>% # Some of these have leading or trailing whitespace
spread(taxLevels, tag) %>%
dplyr::select(oldGroups, Phylum, Class, Order, Family, Genus) %>%
pass -> taxGroupMapping
write_csv(taxGroupMapping, 'tables/taxGroupMapping.csv')
# Print out each otu table (relative abundances).
walk2(psDf1b$taxLevels, psDf1b$OTU, 
      ~write.csv(.y, file = paste0("tables/OTU/otu_",.x, ".csv")))
# Print out each otu table (counts).
walk2(psDf1b$taxLevels, psDf1b$OTUCount, 
      ~write.csv(.y, file = paste0("tables/OTU/otuCount_",.x, ".csv")))
# Print out each taxonomy table.
walk2(psDf1b$taxLevels, psDf1b$Tax, 
      ~write_csv(.y, path = paste0("tables/Tax/tax_",.x, ".csv")))

Response to reviewers: Richness

The reviewer asks if it associates with MDS1. I’ll check for associations with immunogenicity as well.

Calculate the alpha diversity values for psN1

bN1 <- breakaway(psN1)

Initial look at alpha diveristy values.

plot(bN1)

Large confidence intervals. Also, probably best to examine in log space going forward.

Range of breakaway estimates

summary(bN1)$estimate %>% range
[1] 151.0282 441.4724

Richness vs MDS1

btN_MDS <- betta(summary(bN1)$estimate,
              summary(bN1)$error,
              make_design_matrix(psN1, "MDS1")
)
btN_MDS
$table
            Estimates Standard Errors p-values
(Intercept)  243.9720        16.82711    0.000
predictors   -31.7198        27.31450    0.246

$cov
            (Intercept) predictors
(Intercept)    287.4724    57.2095
predictors      57.2095   757.4669

$ssq_u
[1] 1932.271

$homogeneity
[1] 6.250059e+01 1.546382e-06

$global
[1] 215.6907   0.0000

$blups
 [1] 190.9549 235.6741 241.4954 196.3625 222.4246 270.5355 262.2978 280.2053 194.4011 282.1363 231.4248 227.0622 253.5892
[14] 260.3696 266.7135 248.6283 296.0946 232.9051 201.2419 264.0743 264.8205

Not in any way that is statistically significant (p = 0.246)

Some pre-computing

richEsts <- bN1 %>% map_dbl("estimate")

richCI <- bN1 %>% map_df("interval") %>% t %>% as.data.frame() %>% rename(richLb = V1, richUb = V2) %>% merge(as.data.frame(richEsts), by = "row.names") %>% transform(row.names = Row.names, richEst = richEsts, Row.names = NULL, richEsts = NULL)
# need to add mds1 to this, or just tack this all onto psN1rich

psN1rich <- psN1
sample_data(psN1rich) <- merge(psN1@sam_data, richCI, by = "row.names") %>% transform(row.names = Row.names, Row.names = NULL)

Plot richness vs mds1

psN1rich %>% sample_data %>% as.data.frame %>%
  ggplot(aes(x = MDS1, y = richEst)) + geom_point() #+ geom_errorbar()

As above but with error bars.

psN1richP <- psN1rich %>% sample_data %>% as.data.frame %>%
  ggplot(aes(x = MDS1, y = richEst, ymin = richLb, ymax = richUb)) + geom_point() + geom_errorbar() + scale_y_log10()
psN1richP

Unifrac Distance vs Richness

The reviewer actually asked whether unifrac distance associates with alpha diversity. I’ve done that with MDS1, but not distance per se. Lets do one kernel regression, where we ask whether unifrac distance associates with richness.

Kernel regression, weighted unifrac vs richness

mirRich <- MiRKAT(y = richEsts, Ks = wufKN2, out_type = "C", method = 'permutation', nperm = jnperm)
mirRich
[1] 0.2288

Very similar p-value to running betta against MDS1.

PCoA figure that compares MDS1 and MDS2 to richness

psN1richMDSP<- psN1rich %>%
sample_data() %>%
ggplot(aes(x = MDS1, y = MDS2)) + geom_point(aes(fill = richEst), size = 5, stroke = 1, shape = 21) +
viridis::scale_fill_viridis(name = 'richEst', direction = 1, option = "viridis") +
  coord_fixed(sqrt(psN2.pcoa$CA$eig[2]/psN2.pcoa$CA$eig[1])) +
#scale_colour_manual(name = 'gp41 Primary', values = c('black', 'grey70')) + 
cowplot::theme_cowplot()
psN1richMDSP

Two text examples, where we wil see if we can relate richness to a parameter

Here is a discrete example originally I used Gp120 month 6.5. Switching to gp41 month 6.5, since subsequent analyis suggested that it does show a relationship, and so makes a more useful exemplar

gpLocmtx <- cbind(1,
  psN1 %>% sample_data %>% as.matrix %>% as.data.frame %>% pull(IgG_gp41_Month_6.5) %>% as.character %>% as.numeric %>% medcode
)
colnames(gpLocmtx) = c("(Intercept)", "predictors")
btNLoc <- betta(summary(bN1)$estimate,
              summary(bN1)$error,
              gpLocmtx
)
btNLoc
$table
            Estimates Standard Errors p-values
(Intercept) 209.10249        13.30004    0.000
predictors   76.38459        22.30439    0.001

$cov
            (Intercept) predictors
(Intercept)    274.4924  -274.4924
predictors    -274.4924   771.9782

$ssq_u
[1] 780.9728

$homogeneity
[1] 24.0326989  0.1949011

$global
[1] 323.1194   0.0000

$blups
 [1] 184.0879 256.1046 284.6409 195.8995 211.0920 290.4048 217.0720 288.8596 204.3579 291.4781 213.4344 210.8080 231.2472
[14] 222.1869 291.5582 286.9768 296.3785 285.9490 200.8389 280.4231 287.5847

Compare richness to each immunogenicity parameter.

Step 1, make a data frame with BN1, but pub_id for each sample

Pre calculations

SampleNameToPubId <- psN1 %>% sample_data %>% as.data.frame %>% rownames_to_column() %>% as.tibble %>% dplyr::select(rowname, pub_id) %>% na.omit()
Setting class(x) to multiple strings ("tbl_df", "tbl", ...); result will no longer be an S4 object

A function that takes immunogenicity data, x (which we will pass in with tidyverse mapping functions), ad, the breakaway richness summary, and a transformation, defaults to medcode2 of the immunogenicity data.

immuneValpha <- function(x, ad = bN1, transformation = medcode2){
  #x <- arrange(x, rowname)
  x2 <- right_join(x, SampleNameToPubId, by = 'pub_id')
  x3 <- x2[is.finite(x2$mag),]
  
  ad2 <- ad[is.finite(x2$mag)]
  class(ad2) <- c("alpha_estimates", "list")
  
  gpXmtx = cbind(1, transformation(x3$mag))
  #gpXmtx
  
   thing <- betta(summary(ad2)$estimate,
               summary(ad2)$error,
               gpXmtx
 )
   out <- as.data.frame(t(thing$table[2,]))
   #colnames(out) = "pval"
   
   # add R^2 value, from simple pearson correlation
   locCor <- cor(summary(ad2)$estimate, gpXmtx[,2], method = "pearson")
   r2 <- locCor^2
   out2 <- data.frame(out, r2)
   
   out2
}

# test a single use case
immuneValpha(use.immune %>% filter(visitno == 9, type == "IgG", antigen == "Con.6.gp120.B"), bN1, transformation = medcode)

Actually run the analysis, raw table

immuneAlphaCompiled <- use.immune %>% 
  group_by(type, antigen, month) %>%
  do(immuneValpha(.)) %>%
  ungroup # if you forget to ungroup, pvalue calculations don't work correctly
immuneAlphaTable <- immuneAlphaCompiled %>% mutate(qval = p.adjust(`p.values`, method = "BH"))
immuneAlphaTable

Pretty up the table above for publication

conciseImmuneAlphaTable <- immuneAlphaTable %>% 
  dplyr::select(Type = type, Antigen = antigen, Month = month, Coef=Estimates, R2 = r2, P=`p.values`, Q = qval)
toAlphaTable <- conciseImmuneAlphaTable %>%
  mutate(
    Coef = cell_spec(format(Coef, digits = 3), "html",
                     bold = ifelse(P < 0.05, T, F),
                     italic = ifelse(P < 0.05 & Coef < 0, T, F),
                     background = ifelse(P < 0.05, ifelse(Coef < 0, "lightsalmon", "lightblue"), "")),
    R2 = cell_spec(round(R2, digits = 3), "html"),
    P = cell_spec(format_round(P, 3), "html",
                         bold = ifelse(P < 0.05, T, F),
                         background = ifelse(P < 0.05, 'yellow', '')
    ),
    Q = cell_spec(format_round(Q, 3), "html",
                         bold = ifelse(Q < 0.2, T, F),
                         background = ifelse(Q < 0.2, 'lightyellow', '')
    )
  ) %>%
   mutate(Antigen = gsub('ANY.ENV.PTEG', 'Any ENV PTEG', Antigen)) %>%
  mutate(Antigen = gsub('gp70_B.CaseA_V1_V2', 'gp70 B.CaseA V1-V2', Antigen))

toAlphaTable %>% kable("html", escape = F, digits = 3, align = 'c') %>%
  kable_styling("striped", "hover", full_width = F)  %>%
  collapse_rows(columns = 1:2, latex_hline = "full") %>%
  pass-> conciseAlphaTable.html

conciseAlphaTable.html %>% cat(file = 'tables/conciseAlphaTable.html')

conciseAlphaTable.html
Type Antigen Month Coef R2 P Q
CD4+ Any ENV PTEG 6.5 40.69 0.033 0.041 0.091
12.0 44.76 0.006 0.013 0.037
IgA gp41 0.0 -1.84 0.032 0.946 0.946
6.5 16.30 0.145 0.534 0.628
12.0 68.38 0.211 0.017 0.042
p24 0.0 5.46 0.007 0.827 0.871
6.5 56.58 0.16 0.010 0.033
12.0 32.85 0.014 0.168 0.280
IgG Con.6.gp120.B 6.5 21.04 0.003 0.348 0.535
12.0 17.55 0.001 0.440 0.587
gp41 0.0 17.12 0.001 0.503 0.628
6.5 76.38 0.247 0.001 0.010
12.0 61.02 0.094 0.009 0.033
gp70 B.CaseA V1-V2 6.5 -5.83 0.006 0.812 0.871
12.0 70.51 0.062 0.000 0.000
p24 0.0 -30.85 0.033 0.129 0.235
6.5 60.60 0.117 0.010 0.033
12.0 47.26 0.234 0.074 0.148
ZM96.gp140 6.5 21.89 0.036 0.387 0.553
12.0 66.78 0.095 0.002 0.013

As above but with continuous immunogenicity data.

immuneAlphaCompiledGaus <- use.immune %>% 
  group_by(type, antigen, month) %>%
  do(immuneValpha(., transformation = jac_box_cox_2)) %>% 
  ungroup
immuneAlphaTableGaus <- immuneAlphaCompiledGaus %>% mutate(qval = p.adjust(`p.values`, method = "BH"))
immuneAlphaTableGaus
conciseImmuneAlphaTableGaus <- immuneAlphaTableGaus %>% 
  dplyr::select(Type = type, Antigen = antigen, Month = month, Coef=Estimates, R2 = r2, P=`p.values`, Q = qval)
toAlphaTableGaus <- conciseImmuneAlphaTableGaus %>%
  mutate(
    Coef = cell_spec(format(Coef, digits = 3), "html",
                     bold = ifelse(P < 0.05, T, F),
                     italic = ifelse(P < 0.05 & Coef < 0, T, F),
                     background = ifelse(P < 0.05, ifelse(Coef < 0, "lightsalmon", "lightblue"), "")),
    R2 = cell_spec(ifelse(R2 < 0.01, format(R2, digits = 2),round(R2, digits = 2)) , "html"),
    P = cell_spec(format_round(P, 3), "html",
                         bold = ifelse(P < 0.05, T, F),
                         background = ifelse(P < 0.05, 'yellow', '')
    ),
    Q = cell_spec(format_round(Q, 3), "html",
                         bold = ifelse(Q < 0.2, T, F),
                         background = ifelse(Q < 0.2, 'lightyellow', '')
    )
  ) %>%
   mutate(Antigen = gsub('ANY.ENV.PTEG', 'Any ENV PTEG', Antigen)) %>%
  mutate(Antigen = gsub('gp70_B.CaseA_V1_V2', 'gp70 B.CaseA V1-V2', Antigen))

toAlphaTableGaus %>% kable("html", escape = F, digits = 3, align = 'c') %>%
  kable_styling("striped", "hover", full_width = F)  %>%
  collapse_rows(columns = 1:2, latex_hline = "full") %>%
  pass-> conciseAlphaTableGaus.html

conciseAlphaTableGaus.html %>% cat(file = 'tables/conciseAlphaTableGaus.html')

conciseAlphaTableGaus.html
Type Antigen Month Coef R2 P Q
CD4+ Any ENV PTEG 6.5 30.50 0.13 0.016 0.064
12.0 27.49 9.9e-04 0.094 0.235
IgA gp41 0.0 -6.51 0.03 0.718 0.818
6.5 2.15 0.06 0.890 0.890
12.0 32.88 0.09 0.045 0.150
p24 0.0 -6.77 2.8e-04 0.714 0.818
6.5 15.87 0.03 0.374 0.680
12.0 25.20 0.06 0.226 0.452
IgG Con.6.gp120.B 6.5 6.48 2.4e-06 0.706 0.818
12.0 5.36 7.9e-05 0.736 0.818
gp41 0.0 10.14 1.2e-04 0.510 0.774
6.5 29.66 0.18 0.002 0.010
12.0 31.71 0.17 0.000 0.000
gp70 B.CaseA V1-V2 6.5 3.24 2.1e-03 0.861 0.890
12.0 36.15 0.03 0.000 0.000
p24 0.0 -14.39 0.1 0.421 0.702
6.5 21.71 0.14 0.175 0.389
12.0 31.96 0.21 0.060 0.171
ZM96.gp140 6.5 8.40 4.7e-04 0.542 0.774
12.0 32.21 0.09 0.000 0.000

Richness vs alpha above, but this time colorcoding by group.

psN1richP_gp41m6 <- psN1rich %>% sample_data %>% as.data.frame %>%
  ggplot(aes(x = MDS1, y = richEst, ymin = richLb, ymax = richUb, fill = as.factor(medcode_hl(IgG_gp41_Month_6.5)))) + geom_point(shape = 21, size = 4) + geom_errorbar() + scale_y_log10() + scale_fill_viridis_d(direction = -1, name = 'gp41 Primary Timepoint') + labs(x = "MDS1", y = "Richness (# ASVs)") + cowplot::theme_cowplot()
psN1richP_gp41m6

Summary figure for of alpha

Combined figure

par <- options()
#options(repr.plot.width=6, repr.plot.height= 11)
#g <- grid.arrange(wuford_gp41, wuford_gp120, ncol = 2)
g <- cowplot::plot_grid(psN1richMDSP, psN1richP_gp41m6, ncol = 1, labels = c("A", "B"), label_size = 24, align = "v")
g


cowplot::save_plot('figures/richnessMDS1Gp41.png', g, base_width = 6, base_height = 8)

Next question. Do the donor and non donor groups ever have different immune responses?

This is in response to a reviewr comment. Strategy, write a function to, for one variable of interest, compare the groups, as I did for unifrac distance. I’ll use a simple logistic regression here. Similar to CapVar. I want a use.immune data set that includes whether people are a donor as a column, but that

immune.data %>%
mutate(isDonor = pub_id %in% muDoners) %>%
filter(
    (type == 'IgG' & 
    antigen %in% ants1 &
    month %in% c(6.5,12)
    ) |
    (type %in% c('IgG', 'IgA') &
     antigen %in% ants2 &
     month %in% c(0,6.5,12)
    ) |
    type == 'CD4+' &
    antigen == 'ANY.ENV.PTEG' &
    month %in% c(6.5, 12)
      )-> allparticipants.immune
head(allparticipants.immune)
allparticipants.test <- allparticipants.immune %>% filter(visitno ==9, antigen == "Con.6.gp120.B",type == "IgG")
head(allparticipants.test)
donorTest <- function(x, transformation = medcode2, family = 'binomial'){
  loc_glm <- glm(transformation(mag) ~  isDonor, data = x, family = family)
  loc_glm %>% broom::tidy() %>% filter(term == 'isDonorTRUE') %>% dplyr::select(-term)
}
donorTest(allparticipants.test)

Do on everything

allparticipants.immune %>%
  filter(ct == 'T') %>%
  group_by(type, antigen, visitno) %>%
  do(data.frame(donorTest(.))) %>%
  pass-> DonorEffectTable
DonorEffectTable

Ok. There appears to be no significant difference in magnitude group for any category.

allparticipants.immune %>%
  filter(ct == 'T') %>%
  group_by(type, antigen, visitno) %>%
  do(data.frame(donorTest(.,  transformation = function(x){jac_box_cox_2(x)},
                     family = 'gaussian'))) %>%
  pass-> DonorEffectGaus

Monkeys, I guess its debug aclock.

DonorEffectGaus

Same deal when I do a normal logistic regression.

save.image(file = "workspace.Rdata")
'package:stats' may not be available when loading
LS0tCnRpdGxlOiBBbiBSIE1hcmtkb3duIGRvY3VtZW50IGNvbnZlcnRlZCBmcm9tICJNYXIyMDE4XzA5Ni5pcHluYiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhlIGdvYWwgb2YgdGhpcyBOb3RlYm9vayBpcyB0byB0YWtlIHRoZSBwYXJ0cyBvZiBOb3ZlbWJlclBoeWxvUHJvYy5pcHluYiB0aGF0IEkgZXhwZWN0IHRvIGdvIGludG8gdGhlIGFjdHVhbCBtYW51c2NyaXB0LgoKIyBGaWd1cmUgT3V0bGluZQoKKiBGaWd1cmUgMS4gQW50aWJvZGllcyBvdmVyIHRpbWUuCgoqIEZpZ3VyZSAyLiBXZWlnaHRlZCB1bmlmcmFjIFBDb0EKCiogVGFibGUgMS4gS2VybmVsIHJlZ3Jlc3Npb24gYW5kIHdlaWdodGVkIHVuaWZyYWMgMSB0ZXN0IC0gYmlub21pYWwKCiogVGFibGUgUzEuIEtlcm5lbCByZWdyZXNzaW9uIGFuZCB3ZWlnaHRlZCB1bmlmcmFjIDEgdGVzdCAtIGdhdXNzaWFuCgoqIFRhYmxlIFMyLiBXZWlnaHRlZCB1bmlmcmFjIGNvbXBvbmVudHMgMi1OCgoqIEZpZ3VyZSBTMS4gSmVuc2VuIFNoYW5ub24gYXQgZGlmZmVyZW50IGFnZ2xvbWVyYXRpb24gbGV2ZWxzLgoKKiBGaWd1cmUgUzIuIFN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgKHA8IDAuMDUsIHEgPCAwLjIpIGZhbWlseSBnZW51cyBhbmQgc3BlY2llcyBhYnVuZGFuY2VzIChjbHIgdHJhbnNmb3JtZWQpIHJlZ3Jlc3NlZCBhZ2FpbnN0IHdlaWdodGVkIHVuaWZyYWMgMS4KCiogVGFibGUgUzMuIEFsbCBmYW1pbHkgLSBnZW51cyBhbmQgc3BlY2llcyB2cyBhbnRpYm9keSB2cyBnbG0gc2NvcmVzLgoKKiBGaWd1cmUgMy4gU3RhY2tlZCBiYXJzIG9mIGtleSBncm91cHMgb3JkZXJlZCBieSB3ZWlnaHRlZCB1bmlmcmFjIGF4aXMgMS4KCiogRmlndXJlIFMzLiBHcm91cHMgYXNzb2NpYXRlZCB3aXRoIElnR3MuCgoqIEZpZ3VyZSA0LiBQcm9wb3J0aW9uYWxpdHkgaGVhdC1tYXAKCiogT3RoZXIgc3VwcGxlbWVudHM6IFRoZSBlbnRpcmUgZGF0YSB0YWJsZQogICAgKiBCYXNpY2FsbHkgdGhlIGNvbXBvbmVudHMgb2YgcHNOMiwgZXhjZXB0IHRoZSB0cmVlPwogICAgKiBPciBjYW4gSSBqdXN0IHJlbGVhc2UgdGhlIGlucHV0IGRhdGEgZmlsZXM/CgozLzIwLzIwMTcKUmUtcmFuIHdob2xlIHBpcGVsaW5lIGVuZC10by1lbmQuIEhpdHMgb24gZXZlcnl0aGluZyAoaW5jbHVkaW5nIGlnZyBncDQxIDAgZGF5KSBleGNlcHQgb25seSB0cmVuZGluZyBvbiBncDQxIE1vbnRoIDYuNS4gSG93ZXZlciBJIGRvbid0IHNlZSBmYW1pbHkgbGV2ZWwgZ3JvdXBzIGZvciBncDQxIHRoYXQgcmVsYXRlIHRvIGNvbW11bml0eSBzdHJ1Y3R1cmUgKHEgPCAwLjIsIHAgPCAwLjA1KS4gSSBtYXkganVzdCB0cnkgcnVubmluZyBldmVyeXRoaW5nIGVuZCB0byBlbmQgYSBmZXcgbW9yZSB0aW1lcyB0byBzZWUgd2hhdCBoYXBwZW5zLiBUaGlzIGJlY2F1c2UgSSB3YW50IHRvIHNlZSBob3cgY29uc2lzdGFudCAob3Igb3RoZXJ3aXNlKSB0aGUgcmVzdWx0cyBhcmUgYmV0d2VlbiBydW5zLgpBbHNvLCBJJ20gZ29pbmcgdG8gc3RhcnQgc2V0dGluZyBzZWVkcyBub3cuCgojIExvYWRpbmcgbGlicmFyaWVzLCBmdW5jdGlvbnMgYW5kIGRhdGEKCiMjIExpYnJhcmllcwoKYGBge3J9CiMgb25seSB1c2UgbGlicmFyeSBwYXRocyBpbiB0aGUgYW5hY29uZGEgZW52aXJvbm1lbnQKCiMubGliUGF0aHMoZ3JlcCgnYW5hY29uZGEzJywgLmxpYlBhdGhzKCksIHZhbHVlID0gVCkpCmBgYAoKYGBge3J9Ci5saWJQYXRocygpCmBgYAoKYGBge3J9ClIudmVyc2lvbgpgYGAKCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoKYGBge3J9CiMgaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvNDYzNTQ4MjYvaGF2ZS1hLWZ1bmN0aW9uLXRoYXQtY2FsbHMtbGlicmFyeS1hbmQtdGFrZXMtZWl0aGVyLWEtcGFja2FnZS1vci1pdHMtbmFtZS1hcy1pbnAKCgojIEFsc28gcmV0dXJuIHBhY2thZ2UgdmVyc2lvbiB3aGVuIGxvYWRpbmcgaW4gcGFja2FnZXMKIyBhY2NlcHQgc3RyaW5ncyBvciBmdW5jdGlvbnMKbGlidmVyIDwtIGZ1bmN0aW9uKHBhYyl7CgogICAgcGFjIDwtIGFzLmNoYXJhY3RlcihzdWJzdGl0dXRlKHBhYykpCiAgICBsaWJyYXJ5KHBhYywgY2hhcmFjdGVyLm9ubHk9VFJVRSkKICAgIHBhY2thZ2VWZXJzaW9uKHBhYykKICAgIH0KYGBgCgpgYGB7cn0KI2xpYnZlcigiZGFkYTIiKQojbGlidmVyKCJnZ3Bsb3QyIikKYGBgCgpgYGB7cn0KbGlidmVyKCJDYWlybyIpCmBgYAoKYGBge3J9CiMgTXVjaCBvZiB0aGUgZGF0YSBoYW5kbGluZwpsaWJ2ZXIoJ3BoeWxvc2VxJykKYGBgCgpgYGB7cn0KIyBBIGJ1bmNoIG9mIGVudmlyb25tZW50cywgaW5jbHVkaW5nIGdncGxvdCwgZHBseXIsIHRpZHlyLCBhbmQgYnJvb20sIHdoaWNoIEkgdXNlIGEgbG90CmxpYnZlcigndGlkeXZlcnNlJykKYGBgCgpgYGB7cn0KIyBNb3N0bHkgZm9yIGNvbmNhdGVuYXRpbmcgZ2dwbG90cwpsaWJyYXJ5KGdyaWRFeHRyYSk7IHBhY2thZ2VWZXJzaW9uKCJncmlkRXh0cmEiKQpgYGAKCmBgYHtyfQojIEkgdXNlIHRoaXMgc3VycHJpc2luZ2x5IG5vdCBhIGxvdCBoZXJlLgpsaWJyYXJ5KHZlZ2FuKTsgcGFja2FnZVZlcnNpb24oInZlZ2FuIikKYGBgCgpgYGB7cn0KIyBGb3IgbWFraW5nIHRyZWVzCiMgbGlidmVyKCdwaGFuZ29ybicpCmBgYAoKYGBge3J9CiMgQSBwcmVyZXF1ZXNpdGUgdG8gcGhhbmdvcm4KIyBsaWJ2ZXIoIkRFQ0lQSEVSIikKYGBgCgpgYGB7cn0KIyBTb21lIHByZS1wcm9jZXNzaW5nIHN0dWZmCiMgbGlidmVyKCJkYWRhMiIpCmBgYAoKYGBge3J9CiMgSSB1c3VhbGx5IHJlc2hhcGUgd2l0aCB0aWR5dmVyc2UgdG9vbHMgbm93LCBidXQgbWVsdCBhbmQgY2FzdCBhcmUgb2Z0ZW4gZWFzaWVyIGluIGEgcGluY2gKIyBsaWJ2ZXIoInJlc2hhcGUyIikKYGBgCgpgYGB7cn0KIyBGb3IgcmVwbGFjaW5nIE5hTnMgd2l0aG91dCB0b28gbXVjaCB0aG91Z2h0LgojIGxpYnZlcigiaW1wdXRlTWlzc2luZ3MiKQpgYGAKCmBgYHtyfQojIERlYWwgd2l0aCBwcm9wb3J0aW9uYWwgZGF0YSwgZXNwZWNpYWxseSB1c2VmdWwgZm9yIGNhbGN1bGF0aW5nIHByb3BvcnRpb25hbGl0eSBwaGkKbGlidmVyKCdjb21wb3NpdGlvbnMnKQpgYGAKCmBgYHtyfQojIFdvcmtzIHdpdGggdGlkeXZlcnNlIHRvIG1ha2UgbW9kZWwgb3V0cHV0IHRpZHkKbGlidmVyKCdicm9vbScpCmBgYAoKYGBge3J9CiMgTWFrZSBwcmV0dHkgdGFibGVzCmxpYnZlcigna25pdHInKQpsaWJ2ZXIoJ2thYmxlRXh0cmEnKQpgYGAKCmBgYHtyfQojIExldCB0aG9zZSBwcmV0dHkgdGFibGVzIGFjdHVhbGx5IHNob3cgdXAgaW4gYSBqdXB5dGVyIG5vdGVib29rCiNsaWJyYXJ5KCdJUmRpc3BsYXknKQpgYGAKCmBgYHtyfQojIEZvciBib290c3RyYXBwaW5nCmxpYnZlcignYm9vdCcpCmBgYAoKYGBge3J9CiMgQ2FsY3VsYXRlIGtlcm5lbCByZWdyZXNzaW9ucwpsaWJ2ZXIoIk1pUktBVCIpCmBgYAoKYGBge3J9CmxpYnZlcigiY2FyIikKYGBgCgpgYGB7cn0KI2xpYnZlcihtY2x1c3QpCmBgYAoKYGBge3J9CiNsaWJ2ZXIoY2hlbW9tZXRyaWNzKQpgYGAKCmBgYHtyfQpsaWJ2ZXIocHVycnJseXIpCmBgYAoKYGBge3J9CmxpYnZlcigncXZhbHVlJykKYGBgCgpgYGB7cn0KbGlidmVyKCJicmVha2F3YXkiKQpgYGAKCgojIyBGdW5jdGlvbnMKCkkgaGF2ZSBwdXQgdGhlIGZ1bmN0aW9ucyBpbiBhIGxpYnJhcnkgZmlsZQoKYGBge3J9CnNvdXJjZSgnbGlicmFyaWVzL2xpYnJhcnkwOTYuUicpCmBgYAoKIyMgRGF0YQoKYGBge3J9CiMgU2V0IHVwT3JpZ2luYWwgdG8gZmFsc2UsIGlmIHlvdSB3YW50IHRvIHVzZWQgdXNlci1yZXByb2Nlc3NlZCBkYXRhLgojIFJlc3VsdHMgbWF5IGRpZmZlciBzbGlnaHRseSBmcm9tIHRob3NlIGluIHRoZSBtYW51c2NyaXB0IGR1ZSB0byBpbnRlci1ydW4gdmFyaWF0aW9uCiMgZXNwZWNpYWxseSBpbiB0aGUgdHJlZS1pbmcgYWxnb3JpdGhtLgogdXBPcmlnaW5hbCA8LSBUUlVFCiMgdXBPcmlnaW5hbCA8LSBGQUxTRQpgYGAKCmBgYHtyfQojIEZvciBwZXJtdXRhdGlvbiB0ZXN0cywgaG93IGZhc3QgZG8gdGhpbmdzIG5lZWQgdG8gcnVuCiMgOTk5OSBmb3IgbW9zdCBydW5zLCA5OTk5OSBmb3IgcHVibGljYXRpb24gcXVhbGl0eSBvbmVzIHN1Z2dlc3RlZApqbnBlcm0gPC0gOTk5OQpgYGAKCmBgYHtyfQojIERhdGEgcGF0aHMKZ2V0d2QoKQoobWFwcGluZ19maWxlX3BhdGggPC0gZmlsZS5wYXRoKCdkYXRhJywgJ21hcHBpbmdfZmlsZV8wOTZhLmNzdicpKQooaW1tdW5lX2ZpbGVfcGF0aCA8LSBmaWxlLnBhdGgoJ2RhdGEnLCAnaW1tdW5lMDk2Yi5jc3YnKSkKCmlmKHVwT3JpZ2luYWwpewogICAgIHNlcXRhYl9maWxlX3BhdGggPC0gZmlsZS5wYXRoKCdkYXRhJywgJ3NlcXRhYi5ub2NoaW1Ob3YyMDE3LmNzdicpCiAgICAgdGF4YV9maWxlX3BhdGggPC0gZmlsZS5wYXRoKCdkYXRhJywgJ1RheGFOb3YyMDE3LmNzdicpCiAgICAgdHJlZV9wYXRoIDwtIGZpbGUucGF0aCgnZGF0YScsICdwaHlsb2dlbnkwOTZOb3ZUcmVlLnRyZScpCiAgICB9IGVsc2UgewogICAgIHNlcXRhYl9maWxlX3BhdGggPC0gZmlsZS5wYXRoKCdkYXRhMScsICdzZXF0YWIubm9jaGltTWFyMjAxOC5jc3YnKQogICAgIHRheGFfZmlsZV9wYXRoIDwtIGZpbGUucGF0aCgnZGF0YTEnLCAnVGF4YU1hcjIwMTguY3N2JykKICAgICB0cmVlX3BhdGggPC0gZmlsZS5wYXRoKCdkYXRhMScsICdwaHlsb2dlbnkwOTZNYXIyMDE4dHJlLnRyZScpCn0KCnNlcXRhYl9maWxlX3BhdGgKdGF4YV9maWxlX3BhdGgKdHJlZV9wYXRoCmBgYAoKYGBge3J9CiMgU2VxdWVuY2UgZGF0YQpzZXF0YWIubm9jaGltLmRhdGEgPC0gcmVhZC5jc3Yoc2VxdGFiX2ZpbGVfcGF0aCkKCnNlcXRhYk5hbWVzID0gZ3N1YignXFwuJywgJy0nLAogICAgZ3N1YignLmZhc3RxJywgJycsIHNlcXRhYi5ub2NoaW0uZGF0YSRYKQogICAgICAgICAgICAgICAgICAgKQoKc2VxdGFiLm5vY2hpbSA9IGFzLm1hdHJpeChzZXF0YWIubm9jaGltLmRhdGFbLC0xXSkKcm93bmFtZXMoc2VxdGFiLm5vY2hpbSkgPSBzZXF0YWJOYW1lcwpgYGAKCmBgYHtyfQojIFRheGEgbmFtZXMKdGF4YS5kYXRhIDwtIHJlYWQuY3N2KHRheGFfZmlsZV9wYXRoKQp0YXhhID0gdGF4YS5kYXRhWywtMV0KCiMjIEkgcmV2ZXJzZSBjb21wbGVtZW50ZWQgdGhlIHNlcXVlbmNlcyB0byBnZW5lcmF0ZSB0aGUgdGF4b25vbXkKIyAoYnV0IG9ubHkgaW4gdGhpcyBsYXRlc3QgcmUtcnVuLCBub3QgdGhlIG9yaWdpbmFsKQojIyBUaGUgZm9sbG93aW5nIHVuZG9lcyB0aGF0IHJldmVyc2UgY29tcGxlbWVudCB0byBnZXQgb3JpZ2luYWwgc2VxdWVuY2UKI3Jvd25hbWVzKHRheGEpID0gZGFkYTI6OjpyYyh0YXhhLmRhdGFbLDFdKSAKCmlmKHVwT3JpZ2luYWwpewogICAgcm93bmFtZXModGF4YSkgPSAodGF4YS5kYXRhWywxXSl9IGVsc2UgewogICAgcm93bmFtZXModGF4YSkgPSBkYWRhMjo6OnJjKHRheGEuZGF0YVssMV0pIAp9Cgp0YXhhIDwtIGFzLm1hdHJpeCh0YXhhKQpgYGAKCmBgYHtyfQojIE1hcHBpbmcgZmlsZQptYXBwaW5nLmRhdGEgPC0gcmVhZF9jc3YobWFwcGluZ19maWxlX3BhdGgpICU+JQptdXRhdGUocHViX2lkID0gc2FwcGx5KHB1Yl9pZCwgIGZ1bmN0aW9uKHgpIHthcy5udW1lcmljKGdzdWIoIjA5Ni0iLCAiIiwgeCkpfSkpCiNtYXBwaW5nID0gbWFwcGluZy5kYXRhWywtMV0KI3Jvd25hbWVzKG1hcHBpbmcpID0gbWFwcGluZy5kYXRhWywxXQojbWFwcGluZyA8LSBhcy5tYXRyaXgobWFwcGluZykKYGBgCgpgYGB7cn0KaGVhZChtYXBwaW5nLmRhdGEpCmBgYAoKYGBge3J9CiMgSW1tdW5lIERhdGEKaW1tdW5lLmRhdGEwIDwtIHJlYWRfY3N2KGltbXVuZV9maWxlX3BhdGgpCmltbXVuZS5kYXRhIDwtIG11dGF0ZShpbW11bmUuZGF0YTAsIHB1Yl9pZCA9IHNhcHBseShwdWJfaWQsICBmdW5jdGlvbih4KSB7YXMubnVtZXJpYyhnc3ViKCIwOTYtIiwgIiIsIHgpKX0pKQpsZXZlbHMoaW1tdW5lLmRhdGEkYW50aWdlbikgPC0gZ3N1YigiWyAvXSIsICIuIiwgbGV2ZWxzKGltbXVuZS5kYXRhJGFudGlnZW4pKQpgYGAKCmBgYHtyfQpoZWFkKGltbXVuZS5kYXRhKQpgYGAKCmBgYHtyfQojIFBoeWxvZ2VuZXRpYyB0cmVlCnNlcXMgPC0gZGFkYTI6OmdldFNlcXVlbmNlcyhzZXF0YWIubm9jaGltKQpuYW1lcyhzZXFzKSA8LSBzZXFzCgpwdCA8LSBhcGU6OnJlYWQudHJlZShmaWxlPXRyZWVfcGF0aCkKCnB0MiA8LSBwaGFuZ29ybjo6bWlkcG9pbnQocHQpCmBgYAoKYGBge3J9CmltbXVuZS5kYXRhJGFudGlnZW4gJT4lIHVuaXF1ZQpgYGAKClNhdmUgb3B0aW9ucyB0byBhIHZhcmlhYmxlCgpgYGB7cn0KcGFyMCA8LSBvcHRpb25zKCkKYGBgCgojIFByZS1wcm9jZXNzaW5nCgpgYGB7cn0KIyMgbWluaW1hbCBzYW1wbGUgaWRlbnRpZmljYXRpb24gZGF0YQpwdWJfaWRfa2V5IDwtIHVuaXF1ZShpbW11bmUuZGF0YVssYygicHViX2lkIiwgInJ4X2NvZGUiLCAiY3QiKV0pCgpzYW1wbGVfc20wIDwtIGRwbHlyOjpzZWxlY3QobWFwcGluZy5kYXRhLCBTYW1wbGVJRCwgcHViX2lkLCBzZXggPSBTZXgsIG11VmlzaXQgPSBWaXNpdCwgbXVWaXNpdFJhbmsgPSB2aXNpdFJhbmspCnNhbXBsZV9zbSA8LSBsZWZ0X2pvaW4oc2FtcGxlX3NtMCwgcHViX2lkX2tleSwgYnkgPSAicHViX2lkIikgJT4lCmFzLmRhdGEuZnJhbWUgJT4lCnRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKHZhciA9ICJTYW1wbGVJRCIpCiMgcm93bmFtZXMoc2FtcGxlX3NtKQojIGhlYWQoc2FtcGxlX3NtKQpgYGAKCmBgYHtyfQojIE1ha2UgcmF3IHBoeWxvc2VxIG9iamVjdApvdCA8LSBvdHVfdGFibGUoc2VxdGFiLm5vY2hpbSwgdGF4YV9hcmVfcm93cz1GQUxTRSkKCnR0IDwtIHRheF90YWJsZSh0YXhhKQpkaW1uYW1lcyh0dCkgPSBkaW1uYW1lcyh0YXhhKQoKc3BsIDwtIHNhbXBsZV9kYXRhKHNhbXBsZV9zbSkKYGBgCgpwc04gaXMgYSByZWFsbHkgcmF3IHBoeWxvc2VxIG9iamVjdAoqIE9UVSBuYW1lcyBhcmUgZ2l2ZW4gYXMgYWNjZXNzaW9uIG51bWJlcnMKKiBOdW1iZXJzIGFyZSBpbiB0b3RhbCBjb3VudHMKKiBXZSBoYXZlIHNhbXBsZXMgZnJvbSBib3RoIHRpbWUgcG9pbnRzCgpgYGB7cn0KIyBRdWl0ZSByYXcgcGh5bG9zZXEgb2JqZWN0LiBTcGVjaWVzIG5hbWVzIGFyZSBnaXZlbiBhcyBhY2Nlc3Npb24gbnVtYmVycwpwc04gPC0gcGh5bG9zZXEob3QsIHR0LCBzcGwsIHB0MikKCnBzTgpgYGAKCkkgd2FudCB0byBtYWtlIGEgcGh5bG9zZXEgb2JqZWN0IGZvciB1c2UgaW4gZXNzZW50YWxseSBhbGwgb2YgdGhlIHN1YnNlcXVlbnQgYW5hbHlzZXMuCkZlYXR1cmVzIGluY2x1ZGU6CiogU29tZSBiYXNpYyB0YXhvbm9taWMgcHJlLXByb2Nlc3NpbmcuCiAgICAqIE5vIHVuY2hhcmFjdGVyaXplZCBwaHlsYS4KICAgICogT25seSBPVFVzIHRoYXQgc2hvdyB1cCBhdCBsZWFzdCAxMCUgb2YgdGhlIHRpbWUgaW4gdGhlIGZpbmFsIGRhdGEgc2V0IC4KICAgICAgICAqIERvIHRoaXMgYWZ0ZXIgZmlsdGVyaW5nIHNhbXBsZXMuCiogTm8gdGlwLWdsb21taW5nLiBJJ2xsIHNhdmUgdGhhdCB1bnRpbGwgbGF0ZXIuCiogSW1tdW5lIGRhdGEgaXMgaW5jbHVkZWQgaW4gdGhlIHNhbXBsZSBkYXRhIHRhYmxlLiAKICAgICogV2UnbGwgZG8gQW5kcmV3J3MgcmVwcmVzZW50aXRpdmUgSWdHcyBhbmQgSWdBcy4KKiBXZSBvbmx5IGhhdmUgc2FtcGxlcyBmcm9tIHZpc2l0IDEuCiogV2Ugb25seSBoYXZlIHNhbXBsZXMgZnJvbSBleHBlcmVtZW50YWwgKG5vdCBjb250cm9sKSBncm91cHMuCgpgYGB7cn0KaW1tdW5lLmRhdGEgJT4lIHB1bGwodHlwZSkgJT4lIHVuaXF1ZQpgYGAKCmBgYHtyfQojaW1tdW5lLmRhdGEgJT4lIHVuaXRlKHR5cGVfYW50aWdlbiAsdHlwZSwgYW50aWdlbiwgc2VwID0gIl8iKQpgYGAKCmBgYHtyfQppbW11bmUuZGF0YSAlPiUgZHBseXI6OnNlbGVjdChwdWJfaWQsIG1vbnRoLCB0eXBlLCBhbnRpZ2VuLCBtYWcpICU+JSAKZmlsdGVyKG1vbnRoICVpbiUgYygwLCA2LjUsIDEyKSkgJT4lCnVuaXRlKHR5cGVfYW50aWdlbiwgdHlwZSwgYW50aWdlbiwgc2VwID0gIl8iKSAlPiUKdW5pdGUodHlwZV9hbnRpZ2VuX21vbnRoLHR5cGVfYW50aWdlbiwgbW9udGgsIHNlcCA9ICJfTW9udGhfIikgJT4lCnNwcmVhZChrZXkgPSB0eXBlX2FudGlnZW5fbW9udGgsIHZhbHVlID0gbWFnLCBkcm9wID0gVFJVRSkgLT4gaW1tdW5lLnRhYmxlCmBgYAoKYGBge3J9CmltbXVuZS50YWJsZSAlPiUgaGVhZApgYGAKCiMjIEluaXRpYWwgVGF4b25vbWljIGZpbHRlci4KU29tZSBpbnZlc3RlZ2F0aW9uIHN1Z2dlc3RlZCBieSB0aGUgcGh5bG9zZXEgdHV0b3JpYWxzCnRvIGlkZW50aWZ5IHBoeWxhIGZvciByZW1vdmFsLCBhbmQgdG8gaWRlbnRpZnkgYW4gYWJ1bmRhbmNlIHRocmVzaG9sZAoKYGBge3J9CnBzTgpgYGAKCmBgYHtyfQpwc04gJT4lIHN1YnNldF9zYW1wbGVzKCFpcy5uYShwdWJfaWQpKQpgYGAKCmBgYHtyfQojIHNraXAgdGhlIGJsYW5rcwpwc04gJT4lIHN1YnNldF9zYW1wbGVzKCFpcy5uYShwdWJfaWQpKSAlPiUKIyBPVFVzIG11c3QgYmUgY2hhcmFjdGVyaXplZCB0byBwaHlsdW0Kc3Vic2V0X3RheGEoIWlzLm5hKFBoeWx1bSkmICFQaHlsdW0gJWluJSBjKCIiLCAidW5jaGFyYWN0ZXJpemVkIikpIC0+IHBzTl9oYXNQaHlsdW0KcHNOX2hhc1BoeWx1bQojIGZyb20gOTYwIHRvIDkyOSBvdHVzCmBgYAoKSWRlbnRpZnlpbmcgYW5kIHJlbW92aW5nIHBoeWxhIHdpdGggdmVyeSBmZXcgdGF4YSBpbiB0aGVtCgpgYGB7cn0KcHJldmRmID0gYXBwbHkoWCA9IG90dV90YWJsZShwc05faGFzUGh5bHVtKSwKICAgICAgICAgICAgICAgICBNQVJHSU4gPSBpZmVsc2UodGF4YV9hcmVfcm93cyhwc05faGFzUGh5bHVtKSwgeWVzID0gMSwgbm8gPSAyKSwKICAgICAgICAgICAgICAgICBGVU4gPSBmdW5jdGlvbih4KXtzdW0oeCA+IDApfSkKIyBBZGQgdGF4b25vbXkgYW5kIHRvdGFsIHJlYWQgY291bnRzIHRvIHRoaXMgZGF0YS5mcmFtZQpwcmV2ZGYgPSBkYXRhLmZyYW1lKFByZXZhbGVuY2UgPSBwcmV2ZGYsCiAgICAgICAgICAgICAgICAgICAgICBUb3RhbEFidW5kYW5jZSA9IHRheGFfc3Vtcyhwc05faGFzUGh5bHVtKSwKICAgICAgICAgICAgICAgICAgICAgIHRheF90YWJsZShwc05faGFzUGh5bHVtKSkKCnBseXI6OmRkcGx5KHByZXZkZiwgIlBoeWx1bSIsIGZ1bmN0aW9uKGRmMSl7Y2JpbmQobWVhbihkZjEkUHJldmFsZW5jZSksc3VtKGRmMSRQcmV2YWxlbmNlKSl9KQpgYGAKCmBgYHtyfQpmaWx0ZXJQaHlsYSA9IGMoIlZlcnJ1Y29taWNyb2JpYSIsICJUZW5lcmljdXRlcyIsICJFbHVzaW1pY3JvYmlhIiwgIlN5bmVyZ2lzdGV0ZXMiKQpwc05fTWFpblBoeWxhID0gc3Vic2V0X3RheGEocHNOX2hhc1BoeWx1bSwgIVBoeWx1bSAlaW4lIGZpbHRlclBoeWxhKQpwc05fTWFpblBoeWxhCmBgYAoKYGBge3J9CiMgRGV0ZXJtaW5pbmcgYWJ1bmRhbmNlIHRocmVzaG9sZApwcmV2ZGYxID0gc3Vic2V0KHByZXZkZiwgUGh5bHVtICVpbiUgZ2V0X3RheGFfdW5pcXVlKHBzTl9NYWluUGh5bGEsICJQaHlsdW0iKSkKZ2dwbG90KHByZXZkZjEsIGFlcyhUb3RhbEFidW5kYW5jZSwgUHJldmFsZW5jZSAvIG5zYW1wbGVzKHBzTl9oYXNQaHlsdW0pLGNvbG9yPVBoeWx1bSkpICsKICAjIEluY2x1ZGUgYSBndWVzcyBmb3IgcGFyYW1ldGVyCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC4wNSwgYWxwaGEgPSAwLjUsIGxpbmV0eXBlID0gMikgKyBnZW9tX3BvaW50KHNpemUgPSAyLCBhbHBoYSA9IDAuNykgKwogIHNjYWxlX3hfbG9nMTAoKSArICB4bGFiKCJUb3RhbCBBYnVuZGFuY2UiKSArIHlsYWIoIlByZXZhbGVuY2UgW0ZyYWMuIFNhbXBsZXNdIikgKwogIGZhY2V0X3dyYXAoflBoeWx1bSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQpgYGAKCmBgYHtyfQojIERldGVybWluaW5nIGFidW5kYW5jZSB0aHJlc2hvbGQKcHJldmRmMSA9IHN1YnNldChwcmV2ZGYsIFBoeWx1bSAlaW4lIGdldF90YXhhX3VuaXF1ZShwc05fTWFpblBoeWxhLCAiUGh5bHVtIikpCmdncGxvdChwcmV2ZGYxLCBhZXMoVG90YWxBYnVuZGFuY2UsIFByZXZhbGVuY2UgLyBuc2FtcGxlcyhwc05faGFzUGh5bHVtKSxjb2xvcj1QaHlsdW0pKSArCiAgIyBJbmNsdWRlIGEgZ3Vlc3MgZm9yIHBhcmFtZXRlcgogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuMDUsIGFscGhhID0gMC41LCBsaW5ldHlwZSA9IDIpICsgZ2VvbV9wb2ludChzaXplID0gMiwgYWxwaGEgPSAwLjcpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoKSArICB4bGFiKCJUb3RhbCBBYnVuZGFuY2UiKSArIHlsYWIoIlByZXZhbGVuY2UgW0ZyYWMuIFNhbXBsZXNdIikgKwogIGZhY2V0X3dyYXAoflBoeWx1bSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQpgYGAKCiMgQXJlIGRpZmZlcmVuY2VzIGJldHdlZW4gcGFydGljaXBhbnRzIGdyZWF0ZXIgdGhhbiBkaWZmZXJlbmNlcyB3aXRoaW4gcGFydGljaXBhbnRzIGFjY3Jvc3MgdGltZS1wb2ludHM/CgpNYWtlIGEgcGh5bG9zZXEgb2JqZWN0IGxpa2UgcHNOMiBidXQgd2l0aCBhbGwgcGFydGljaXBhbnRzLCBwc04yQQpDb2RlIGNvcGllZCBmcm9tIGFib3ZlLgoKIyMgQ29uc3RydWN0aW5nIHBzTjIKKHBoeWxvc2VxIG9iamVjdCBvZiByZWxhdGl2ZSBhYnVuZGFuY2VzKQoKQW5kIHBzTjEgKHBoeWxvc2VxIG9iamVjdCBvZiBjb3VudHMpCgpwc04xQSBhbmQgcHNOMkEgaW5jbHVkZSBhbGwgcGFydGljaXBhbnRzLCBhbmQgd2lsbCBiZSB1c2VkIHRvIGxvb2sgYXQgdmFyaWFiaWxpdHkgd2l0aGluIHBhcnRpY2lwYW50cwoKYGBge3J9CnBzTiAlPiUKIyBhZGQgYWxsIHRoZSBpbW11bmUgZGF0YQpwaHlsb19qb2luKGltbXVuZS50YWJsZSwgYnkgPSAicHViX2lkIikgJT4lCiMgb25seSB1c2UgZGF0YSBmcm9tIGh1bWFucyAobm8gZXh0cmFjdGlvbiBjb250cm9scykKc3Vic2V0X3NhbXBsZXMoaXMuZmluaXRlKG11VmlzaXRSYW5rKSkgJT4lCiMgb25seSBvdHVzIGZyb20ga25vd24gdGF4YSB0aGF0IHNob3cgdXAgZnJlcXVlbnRseSBlbm91Z2gKc3Vic2V0X3RheGEoIWlzLm5hKFBoeWx1bSkmICFQaHlsdW0gJWluJSBjKCIiLCAidW5jaGFyYWN0ZXJpemVkIikpICU+JQpzdWJzZXRfdGF4YSghUGh5bHVtICVpbiUgZmlsdGVyUGh5bGEpICU+JQojIG9ubHkgb3R1cyB0aGF0IHNob3cgdXAgaW4gYXQgbGVhc3QgMTAlIG9mIHNhbXBsZXMKcHJldmFsZW5jZV9maWx0ZXJfdGF4YSAlPiUKIyBjb252ZXJ0IHRvIHJlbGF0aXZlIGFidW5kYW5jZQoKdGFnX3BoeWxvc2VxJT4lCiMgSW5zdGVhZCBvZiBuYW1pbmcgZWFjaCB0YXhvbiB3aXRoIGl0cyBmdWxsIHNlcXVlbmNlLCB3ZSB1c2UgdGhlICJ0YWciIGluc3RlYWQKc3dhcC5waHlsb3NlcS50YXhuYW1lcyAlPiUKcGFzcyAtPiBwc04xQSAjIFNhdmUgcHJlIHJlbGF0aXZlIGFidW5kYW5jZSB0cmFuc2Zvcm1hdGlvbgoKIyBhZGQgaXMtbWFsZQptYW5Db2x1bW4gPC0gcHNOMUEgJT4lIHNhbXBsZV9kYXRhICU+JSBhcygnZGF0YS5mcmFtZScpICU+JSByb3duYW1lc190b19jb2x1bW4gICU+JSBtdXRhdGUoaXNNYWxlID0gdGVzdElzTWFsZVZlYyhzZXgpKSAlPiUgZHBseXI6OnNlbGVjdChyb3duYW1lLCBpc01hbGUpCnBzTjFBIDwtIHBoeWxvX2pvaW4ocHNOMUEsIG1hbkNvbHVtbiwgYnkgPSAncm93bmFtZScpCgojIyBwc04yIGlzIGxpa2UgcHNOMSBidXQgd2l0aCByZWxhdGl2ZSBhYnVuZGFuY2VzCnBzTjFBICU+JQp0cmFuc2Zvcm1fc2FtcGxlX2NvdW50cyhmdW5jdGlvbih4KSB7eC9zdW0oeCl9KSAlPiUKIyBUaGUgInRhZyIgaXMgYSBuZXcgbmFtZSB0aGF0IHRha2VzIGludG8gYWNjb3VudCB0aGUgcmVzdCBvZiB0aGUgdGF4b25vbXkgZGF0YQojIHRoZSB0YWcgbWF5IG5lZWQgdG8gYmUgdXBkYXRlZCBhZnRlciBhbnkgYWdnbG9tZXJhdGlvbgpwYXNzLT4gcHNOMkEKYGBgCgpgYGB7cn0KIyBmaWx0ZXIgdG8ganVzdCBtaWNyb2Jpb21lIHZpc2l0IDEgYW5kIGV4cGVyZW1lbnRhbCB0cmVhdG1lbnRzCnBzTjFBICU+JQpzdWJzZXRfc2FtcGxlcyhtdVZpc2l0UmFuayA9PSAxKSAlPiUKc3Vic2V0X3NhbXBsZXMoY3QgPT0gIlQiKSAlPiUKcGFzcyAtPiBwc04xCgpwc04yQSAlPiUKc3Vic2V0X3NhbXBsZXMobXVWaXNpdFJhbmsgPT0gMSkgJT4lCnN1YnNldF9zYW1wbGVzKGN0ID09ICJUIikgJT4lCnBhc3MgLT4gcHNOMgpgYGAKCmBgYHtyfQojIENhbGN1bGF0ZSB3ZWlnaHRlZCB1bmlmcmFjIGRpc3RhbmNlcyBhbmQgcm9sZSB0aG9zZSBpbi4KcHNOMi53dWYgPC0gcGh5bG9zZXE6OmRpc3RhbmNlKHBzTjIsIG1ldGhvZCA9ICJ3dW5pZnJhYyIpCnBzTjIucGNvYSA8LSBjYXBzY2FsZShwc04yLnd1ZiB+IDEpCnBzTjIucGNvYS5kZiA8LSBwc04yLnBjb2EgJT4lIHNjb3JlcyhkaXNwbGF5ID0gInNpdGVzIikgJT4lCiAgICAgICAgYXMuZGF0YS5mcmFtZSAlPiUgCiAgICAgICAgcm93bmFtZXNfdG9fY29sdW1uICU+JSAKICAgICAgICBkcGx5cjo6c2VsZWN0KCdyb3duYW1lJywgJ01EUzEnLCAnTURTMicpICU+JQogICAgICAgIG11dGF0ZShyTURTMSA9IHJhbmsoTURTMSkpICU+JSAjIHJhbmsgb3JkZXIgb2YgTURTMQogICAgICAgIG11dGF0ZShyck1EUzEgPSBmb3JtYXRDKGZvcm1hdCA9ICJkIiwgck1EUzEsIGZsYWcgPSAiMCIsIHdpZHRoPWNlaWxpbmcobG9nMTAobWF4KHJNRFMxKSkpKSkgJT4lCiAgICAgICAgdW5pdGUobmV3bmFtZSwgcnJNRFMxLCByb3duYW1lLCBzZXAgPSAiXyIsIHJlbW92ZSA9IEZBTFNFKSAlPiUKICAgICAgICBkcGx5cjo6c2VsZWN0KC1yck1EUzEpCgpwc04yICU+JQpwaHlsb19qb2luKAogICAgcHNOMi5wY29hLmRmLAogICAgYnkgPSAncm93bmFtZScKKSAtPiBwc04yCgojIyBFdmVuIGlmIHRoZSBkYXRhIGFyZSBjb3VudHMsIAojIyB0aGUgd2VpZ2h0ZWQgdW5pZnJhYyBwY29hIGlzIHN0aWxsIGRvbmUgb24gdGhlIHJlbGF0aXZlIGFidW5kYW5jZXMKcHNOMSAlPiUKcGh5bG9fam9pbigKICAgIHBzTjIucGNvYS5kZiwKICAgIGJ5ID0gJ3Jvd25hbWUnCikgLT4gcHNOMQoKcHNOMkEKcHNOMUEKcHNOMgpwc04xCmBgYAoKIyBEYXRhIEN1cmF0aW9uIFBvc3QgbW9ydHVtCkhvdyBtYW55IHRheGEgd2VyZSBzdGlsbCBwcmVzZW50IGFmdGVyIGVhY2ggZmlsdGVyaW5nIHN0ZXA/CgpgYGB7cn0KIyBGaW5kIG51bWJlciBvZiB0YXhhIGluIGF2YWlsYWJsZSBzYW1wbGVzCnBzTiAlPiUKIyBhZGQgYWxsIHRoZSBpbW11bmUgZGF0YQpwaHlsb19qb2luKGltbXVuZS50YWJsZSwgYnkgPSAicHViX2lkIikgJT4lCiMgZmlsdGVyIHRvIGp1c3QgbWljcm9iaW9tZSB2aXNpdCAxIGFuZCBleHBlcmVtZW50YWwgdHJlYXRtZW50cwpzdWJzZXRfc2FtcGxlcyhtdVZpc2l0UmFuayA9PSAxKSAlPiUKc3Vic2V0X3NhbXBsZXMoY3QgPT0gIlQiKSAlPiUKcHJldmFsZW5jZV9maWx0ZXJfdGF4YSh0aHJlc2ggPSAwKSAlPiUKcGFzcy0+IHBzSW5TYW1wbGVzCihOSW5TYW1wbGVzIDwtIGRpbShvdHVfdGFibGUocHNJblNhbXBsZXMpKVsyXSkKYGBgCgpgYGB7cn0KIyBOdW1iZXIgb2YgdGF4YSB3aXRoIHVuaWRlbnRpZmllZCBwaHlsYQpwc0luU2FtcGxlcyAlPiUKc3Vic2V0X3RheGEoIWlzLm5hKFBoeWx1bSkmICFQaHlsdW0gJWluJSBjKCIiLCAidW5jaGFyYWN0ZXJpemVkIikpICU+JQpwYXNzIC0+IHBzSWRlbnRpZmllZFBoeWx1bQooTlVua1BoeWx1bSA8LSBOSW5TYW1wbGVzIC0gZGltKG90dV90YWJsZShwc0lkZW50aWZpZWRQaHlsdW0pKVsyXSkKYGBgCgpgYGB7cn0KZmlsdGVyUGh5bGEKYGBgCgpgYGB7cn0KIyBQaHlsYSByZW1vdmVkIGJlY2F1c2UgdGhleSBhcmUgaW4gZmlsdGVyUGh5bGEgCiMgLS0gZWFjaCBvZiB3aGljaCBzaG93IHVwIGZld2VyIHRoYW4gMjAgdGltZXMgaW4gdGhlIGRhdGEgc2V0CnBzSWRlbnRpZmllZFBoeWx1bSAlPiUKc3Vic2V0X3RheGEoIVBoeWx1bSAlaW4lIGZpbHRlclBoeWxhKSAlPiUKcGFzcyAtPiBwc05vdFBoeWxhRmlsdGVyZWQKKE5GaWx0UGh5bGEgPC0gZGltKG90dV90YWJsZShwc0lkZW50aWZpZWRQaHlsdW0pKVsyXSAtIGRpbShvdHVfdGFibGUocHNOb3RQaHlsYUZpbHRlcmVkKSlbMl0pCmBgYAoKYGBge3J9CiMgVGF4YSByZW1vdmVkIGJlY2F1c2UgdGhlcmUgd2VyZSBpbiBmZXdlciB0aGFuIDEwJSBvZiB0aGUgc2FtcGxlcwpwc05vdFBoeWxhRmlsdGVyZWQgJT4lCnByZXZhbGVuY2VfZmlsdGVyX3RheGEgJT4lCnBhc3MgLT4gcHNQRlQKZGltKG90dV90YWJsZShwc05vdFBoeWxhRmlsdGVyZWQpKVsyXSAtIGRpbShvdHVfdGFibGUocHNQRlQpKVsyXQpgYGAKCiMgSW1tdW5lIGZpZ3VyZQpIb3cgdG8gcGFydGljaXBhbnRzJyBpbW11bmUgcHJvZmlsZXMgY2hhbmdlIG92ZXIgdGltZT8KCmBgYHtyfQojIFdoZW4gd2VyZSBwYXJ0aWNpcGFudHMgdmFjY2luYXRlZD8KIyBDb3BpZWQgZnJvbSBwcm90b2NvbCBhcGVuZGl4IEUKIyB2aXNpdG5vIDEgaXMgYSBzY3JlZW5pbmcgdmlzaXQsIEkgYXNzaWduIGl0IE5hTgpkYXlUYWJsZSA9IGRhdGEuZnJhbWUoCiAgICB2aXNpdG5vID0gc2VxKGZyb20gPSAxLCB0byA9IDE0LCBieSA9IDEpLAogICAgZGF5ID0gYyhOYU4sIDAsIDE0LCAyOCwgNDIsIDg0LCA5OCwgMTY4LCAxODIsIDE5NiwgMjczLCAzNjQsIDQ1NSwgNTQ1KSwKICAgIG1vbnRoID0gYyhOYU4sIDAsIDAuNSwgMSwgMS41LCAzLCAzLjUsIDYsIDYuNSwgNywgOSwgMTIsIDE1LCAxOCkKKQp2YWMgPC0gZGF0YS5mcmFtZSgKICAgIHZpc2l0bm8gPSBjKDIsIDQsIDYsIDgpCiAgICApCnZhYyA8LSBsZWZ0X2pvaW4odmFjLCBkYXlUYWJsZSwgYnkgPSAndmlzaXRubycpCgp2YWMKYGBgCgpgYGB7cn0KIyBSZXByZXNlbnRpdGl2ZSBhbnRpZ2VucyBmb3IgZnVydGhlciBjb25zaWRlcmF0aW9ucwojIFRoZXNlIGFyZSBlc3NlbnRpYWxseSB6ZXJvIChtYWcgPSAxKSBhdCBiYXNlbGluZQphbnRzMSA8LSBjKCdDb24uNi5ncDEyMC5CJywgJ1pNOTYuZ3AxNDAnLCAnZ3A3MF9CLkNhc2VBX1YxX1YyJykKIyBUaGVzZSBoYXZlIG1lYXN1cmFibGUgYmFzZWxpbmUgbWFnbml0dWRlcwphbnRzMiA8LSBjKCdncDQxJywgJ3AyNCcpCmBgYAoKYGBge3J9CmRvbm9yLmltbXVuZSA8LSAgcHNOMiAlPiUgc2FtcGxlX2RhdGEgJT4lIGFzKCdkYXRhLmZyYW1lJykgJT4lIGRwbHlyOjpzZWxlY3QocHViX2lkKSAlPiUKbGVmdF9qb2luKGltbXVuZS5kYXRhLCBieSA9ICdwdWJfaWQnKQpkb25vci5pbW11bmUgJT4lIGhlYWQKYGBgCgpgYGB7cn0KcHNOICU+JSBzYW1wbGVfZGF0YSAlPiUKYXMoJ2RhdGEuZnJhbWUnKSAlPiUgCmZpbHRlcighaXMubmEocHViX2lkKSkgJT4lCnB1bGwocHViX2lkKSAlPiUKdW5pcXVlICU+JQpwYXNzIC0+IG1pY3JvYmlvbWVDb2hvcnQKYGBgCgpgYGB7cn0KaW1tdW5lLmRhdGEgJT4lIGZpbHRlcihwdWJfaWQgJWluJSBtaWNyb2Jpb21lQ29ob3J0KSAlPiUKcGFzcyAtPiBkb25vci5pbW11bmUKCmRvbm9yLmltbXVuZSAlPiUgaGVhZApgYGAKCmBgYHtyfQpvcHRpb25zKHBhcjApCmlnZ3Bsb3QgPC0gaW1tdW5lLmRhdGEgJT4lCm11dGF0ZShpbkNvaG9ydCA9IHB1Yl9pZCAlaW4lIG1pY3JvYmlvbWVDb2hvcnQpICU+JQpmaWx0ZXIodHlwZSA9PSAnSWdHJywgYW50aWdlbiAlaW4lIGMoYW50czEsIGFudHMyKSkgJT4lCm11dGF0ZShhbnRpZ2VuID0gZmFjdG9yKGFudGlnZW4sIGxldmVscyA9IGMoYW50czIsIGFudHMxKSkpICU+JSAjIHJlb3JkZXIgZmFjZXRzCmdncGxvdChhZXMoeCA9IG1vbnRoLCB5ID1tYWcsIGdyb3VwID0gcHViX2lkLCBjb2xvdXIgPSBpbkNvaG9ydCwgYWxwaGEgPSBpbkNvaG9ydCkpICsKZ2VvbV9saW5lKCkgKwpnZW9tX3BvaW50KCkgKwpnZW9tX3J1ZyhkYXRhID0gdmFjLCBhZXMoeCA9IG1vbnRoKSwgaW5oZXJpdC5hZXMgPSBGLCBjb2xvciA9ICdibHVlJykgKwpmYWNldF9ncmlkKGFudGlnZW4gfiByeF9jb2RlLCBsYWJlbGxlciA9IGxhYmVsX3dyYXBfZ2VuKCkpICsKdGhlbWVfYncoKSArCnRoZW1lKHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDApLAogICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSArCnNjYWxlX3lfbG9nMTAoYnJlYWtzID0gMTBeKDA6NSkpICsKc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJibGFjayIsICJyZWQiKSkgKwpzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzID0gYyguNiwgMSkpICsgCmxhYnMoeSA9ICJCQU1BIFJlc3BvbnNlIE1hZ25pdHVkZSIpCgoKCmdnc2F2ZSgnZmlndXJlcy91c2VpZ2dzQWxsUGFydGljaXBhbnRzLnN2ZycpCiMgVG8gZml4LiBDb250cm9sIGdyb3VwcyBkb24ndCBzaG93IHVwIGluIHRoaXMgdmVyc2lvbi4KCmBgYApgYGB7cn0KaWdncGxvdApgYGAKCgpgYGB7cn0KaWdncGxvdCA8LSBpbW11bmUuZGF0YSAlPiUKbXV0YXRlKGluQ29ob3J0ID0gcHViX2lkICVpbiUgbWljcm9iaW9tZUNvaG9ydCkgJT4lCmZpbHRlcih0eXBlICVpbiUgYygnSWdBJywgJ0NENCsnKSAmIGFudGlnZW4gJWluJSBjKGFudHMyLCAnQU5ZLkVOVi5QVEVHJykpJT4lCm11dGF0ZShhbnRpZ2VuID0gZmFjdG9yKGFudGlnZW4sIGxldmVscyA9IGMoYW50czIsICdBTlkuRU5WLlBURUcnKSkpICU+JSAjIHJlb3JkZXIgZmFjZXRzCmdncGxvdChhZXMoeCA9IG1vbnRoLCB5ID1tYWcsIGdyb3VwID0gcHViX2lkLCBjb2xvdXIgPSBpbkNvaG9ydCwgYWxwaGEgPSBpbkNvaG9ydCkpICsKZ2VvbV9saW5lKCkgKwpnZW9tX3BvaW50KCkgKwpnZW9tX3J1ZyhkYXRhID0gdmFjLCBhZXMoeCA9IG1vbnRoKSwgaW5oZXJpdC5hZXMgPSBGLCBjb2xvciA9ICdibHVlJykgKwpmYWNldF9ncmlkKGFudGlnZW4gfiByeF9jb2RlLCBsYWJlbGxlciA9IGxhYmVsX3dyYXBfZ2VuKCksIHNjYWxlcyA9ICdmcmVlX3knKSArCnRoZW1lX2J3KCkgKwp0aGVtZShzdHJpcC50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwKSwKICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkgKwpzY2FsZV95X2xvZzEwKGJyZWFrcyA9IDEwXmMoCiAgICBzZXEoZnJvbSA9IC0yLCB0byA9IDAsIGJ5ID0gMC4yNSksIHNlcShmcm9tID0gMCwgdG8gPSA1LCBieSA9IDEpCiksIGxhYmVscyA9IGZ1bmN0aW9uKHgpIHJvdW5kKGFzLm51bWVyaWMoeCksIGRpZ2l0cz0zKSkgKwpzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsYWNrIiwgInJlZCIpKSArCnNjYWxlX2FscGhhX21hbnVhbCh2YWx1ZXMgPSBjKC42LCAxKSkgKwpsYWJzKHkgPSAiQkFNQSBSZXNwb25zZSBNYWduaXR1ZGUiKQoKaWdncGxvdAoKZ2dzYXZlKCdmaWd1cmVzL3VzZUlnQUNENEFsbFBhcnRpY2lwYW50cy5zdmcnKQojIFRvIGZpeC4gQ29udHJvbCBncm91cHMgZG9uJ3Qgc2hvdyB1cCBpbiB0aGlzIHZlcnNpb24uCmBgYAoKYGBge3J9CmlnZ3Bsb3QgPC0gZG9ub3IuaW1tdW5lICU+JSBmaWx0ZXIodHlwZSA9PSAnSWdHJywgYW50aWdlbiAlaW4lIGMoYW50czEsIGFudHMyKSkgJT4lCm11dGF0ZShhbnRpZ2VuID0gZmFjdG9yKGFudGlnZW4sIGxldmVscyA9IGMoYW50czIsIGFudHMxKSkpICU+JSAjIHJlb3JkZXIgZmFjZXRzCmdncGxvdChhZXMoeCA9IG1vbnRoLCB5ID1tYWcsIGdyb3VwID0gcHViX2lkKSkgKyBnZW9tX3BvaW50KGFscGhhID0gMC42KSArIGdlb21fbGluZShhbHBoYSA9IDAuNCkgKwpnZW9tX3J1ZyhkYXRhID0gdmFjLCBhZXMoeCA9IG1vbnRoKSwgaW5oZXJpdC5hZXMgPSBGLCBjb2xvciA9ICdibHVlJykgKwpmYWNldF9ncmlkKGFudGlnZW4gfiByeF9jb2RlLCBsYWJlbGxlciA9IGxhYmVsX3dyYXBfZ2VuKCkpICsKdGhlbWVfYncoKSArIHRoZW1lKHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDApLCBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSArCnNjYWxlX3lfbG9nMTAoYnJlYWtzID0gMTBeKDA6NSkpICsKbGFicyh5ID0gIkJBTUEgUmVzcG9uc2UgTWFnbml0dWRlIikKaWdncGxvdApnZ3NhdmUoJ2ZpZ3VyZXMvdXNlaWdncy5zdmcnKSAjIEkgY2FuIG5vIGxvbmdlciBzYXZlIHBuZ3Mgd2l0aCB0cmFuc3BhcmVuY3ksIGdvaW5nIHRvIHN2ZwojIFRvIGZpeC4gQ29udHJvbCBncm91cHMgZG9uJ3Qgc2hvdyB1cCBpbiB0aGlzIHZlcnNpb24uCmBgYAoKIyMgTWFnbml0dWRlIGFuZCB2YXJpYW5jZSBvZiB2YWNjaW5lIHJlc3BvbnNlIHBlciBncm91cAoKYGBge3J9CmNvbG5hbWVzKGltbXVuZS5kYXRhKQpgYGAKClBsYW4uIENhbGN1bGF0ZSBtZWFuIGFuZCB2YXJpYW5jZSBvZiBlYWNoIGFudGlnZW4tdHlwZSBhdCB2aXNpdCA5LCBhbmQgYXQgYmFzZWxpbmUuCgpgYGB7cn0KIwpnZW9tZWFuIDwtIGZ1bmN0aW9uKHgsIG5hLnJtID0gRkFMU0UsIHRyaW0gPSAwLCAuLi4pCnsKZXhwKG1lYW4obG9nKHgsIC4uLiksIG5hLnJtID0gbmEucm0sIHRyaW0gPSB0cmltLCAuLi4pKQp9CiAKZ2Vvc2QgPC0gZnVuY3Rpb24oeCwgbmEucm0gPSBGQUxTRSwgLi4uKQp7CmV4cChzZChsb2coeCwgLi4uKSwgbmEucm0gPSBuYS5ybSwgLi4uKSkKfQpgYGAKCmBgYHtyfQppbW11bmUuZGF0YSAlPiUgZHBseXI6OmZpbHRlcih2aXNpdG5vICVpbiUgYyg5KSwgY3QgPT0gIlQiKSAlPiUgZHBseXI6OnNlbGVjdChwdWJfaWQsIGFudGlnZW4sIHZpc2l0bm8sIG1hZywgbWFnX2JsLCB0eXBlKSAlPiUgZ3JvdXBfYnkoYW50aWdlbiwgdHlwZSkgJT4lCnN1bW1hcml6ZShtZWFuX21hZyA9IGdlb21lYW4obWFnKSwgbWVhbl9ibCA9IGdlb21lYW4obWFnX2JsKSwgc2RfbWFnID0gZ2Vvc2QobWFnKSwgc2RfYmwgPSBnZW9zZChtYWdfYmwpKSAlPiUKbXV0YXRlKHZhcl9vdmVyX21lYW5fbWFnID0gc2RfbWFnXjIvbWVhbl9tYWcsIHZhcl9vdmVyX21lYW5fYmwgPSBzZF9ibF4yL21lYW5fYmwpCmBgYAoKTWVhbiB2YXJpYW5jZSBhbmQgdmFyaWFuY2Ugb3ZlciBtZWFuIG9mIGVhY2ggdmFsdWUuIAoKYGBge3J9CmltbXVuZS5kYXRhICU+JSBkcGx5cjo6ZmlsdGVyKHZpc2l0bm8gJWluJSBjKDkpLCBjdCA9PSAiVCIpICU+JSBkcGx5cjo6c2VsZWN0KHB1Yl9pZCxyeF9jb2RlLCBhbnRpZ2VuLCB2aXNpdG5vLCBtYWcsIG1hZ19ibCwgdHlwZSkgJT4lIGdyb3VwX2J5KHJ4X2NvZGUsIGFudGlnZW4sIHR5cGUpICU+JQpzdW1tYXJpemUobWVhbl9tYWcgPSBnZW9tZWFuKG1hZyksIG1lYW5fYmwgPSBnZW9tZWFuKG1hZ19ibCksIHNkX21hZyA9IGdlb3NkKG1hZyksIHNkX2JsID0gZ2Vvc2QobWFnX2JsKSkgJT4lCm11dGF0ZSh2YXJfb3Zlcl9tZWFuX21hZyA9IHNkX21hZ14yL21lYW5fbWFnLCB2YXJfb3Zlcl9tZWFuX2JsID0gc2RfYmxeMi9tZWFuX2JsKSAlPiUgCmdhdGhlcihrZXkgPSAibWVhcyIsIHZhbHVlID0gInZhbHVlIiwgbWVhbl9tYWcsIG1lYW5fYmwsIHNkX21hZywgc2RfYmwsIHZhcl9vdmVyX21lYW5fbWFnLCB2YXJfb3Zlcl9tZWFuX2JsKSAlPiUgCmdyb3VwX2J5KGFudGlnZW4sIHR5cGUsIG1lYXMpICU+JSBzdW1tYXJpemUobWVhbl92YWwgPSBtZWFuKHZhbHVlKSkgJT4lCnNwcmVhZChrZXkgPSAibWVhcyIsIHZhbHVlID0gIm1lYW5fdmFsIikKYGBgCgpBcyBhYm92ZSwgYnV0IHRoaXMgdGltZSwgY2FsY3VsYXRlZCBzZXBlcmF0ZWx5IGZvciBlYWNoIHRyZWF0bWVudCBncm91cCBhbmQgdGhlbiB0aG9zZSB2YWx1ZXMgYXZlcmFnZWQuClpNOTYgYW5kIGdwNzAgc3VycHJpc2luZ2x5IGxhcmdlIHZhcmlhbmNlIG92ZXIgbWVhbi4gTWF5YmUgc2hvdWxkIGxvb2sgYXQgZGVsdGEgbWFnbml0dWRlCgpgYGB7cn0KaW1tdW5lLmRhdGElPiUgaGVhZApgYGAKCmBgYHtyfQppbW11bmUuZGF0YSAlPiUgCm11dGF0ZShtYWdfZGVsdGEgPSBtYWcgLyBtYWdfYmwpICU+JQptdXRhdGUobWFnX2RlbHRhID0gaWZfZWxzZShtYWdfZGVsdGEgPCAxLCAxLCBtYWdfZGVsdGEpKSAlPiUKZHBseXI6OmZpbHRlcih2aXNpdG5vICVpbiUgYyg5KSwgY3QgPT0gIlQiKSAlPiUgZHBseXI6OnNlbGVjdChwdWJfaWQscnhfY29kZSwgYW50aWdlbiwgdmlzaXRubywgbWFnLCBtYWdfYmwsIG1hZ19kZWx0YSwgdHlwZSkgJT4lIGdyb3VwX2J5KHJ4X2NvZGUsIGFudGlnZW4sIHR5cGUpICU+JQpzdW1tYXJpemUobWVhbl9tYWcgPSBnZW9tZWFuKG1hZyksIG1lYW5fYmwgPSBnZW9tZWFuKG1hZ19ibCksIG1lYW5fZGVsdGEgPSBnZW9tZWFuKG1hZ19kZWx0YSksIHNkX21hZyA9IGdlb3NkKG1hZyksIHNkX2JsID0gZ2Vvc2QobWFnX2JsKSwgc2RfZGVsdGEgPSBnZW9zZChtYWdfZGVsdGEpKSAlPiUKbXV0YXRlKHZhcl9vdmVyX21lYW5fbWFnID0gc2RfbWFnXjIvbWVhbl9tYWcsIHZhcl9vdmVyX21lYW5fYmwgPSBzZF9ibF4yL21lYW5fYmwsIHZhcl9vdmVyX21lYW5fZGVsdGEgPSBzZF9kZWx0YV4yL21lYW5fZGVsdGEpICU+JSAKZ2F0aGVyKGtleSA9ICJtZWFzIiwgdmFsdWUgPSAidmFsdWUiLCBtZWFuX21hZywgbWVhbl9ibCwgc2RfbWFnLCBzZF9ibCwgdmFyX292ZXJfbWVhbl9tYWcsIHZhcl9vdmVyX21lYW5fYmwsIG1lYW5fZGVsdGEsIHNkX2RlbHRhLCB2YXJfb3Zlcl9tZWFuX2RlbHRhKSAlPiUgCmdyb3VwX2J5KGFudGlnZW4sIHR5cGUsIG1lYXMpICU+JSBzdW1tYXJpemUobWVhbl92YWwgPSBtZWFuKHZhbHVlKSkgJT4lCnNwcmVhZChrZXkgPSAibWVhcyIsIHZhbHVlID0gIm1lYW5fdmFsIikgJT4lCmFycmFuZ2UodHlwZSkKYGBgCgojIyBOdW1iZXIgb2YgcGFydGljaXBhbnRzIHBlciBncm91cAoKIyMjIEFsbCBwYXJ0aWNpcGFudHMKCmBgYHtyfQppbW11bmUuZGF0YSAlPiUgCmdyb3VwX2J5KHJ4X2NvZGUpICU+JQpzdW1tYXJpemUoVW5pcXVlX2lkcyA9IG5fZGlzdGluY3QocHViX2lkKSkKYGBgCgojIyMgUGFydGljaXBhbnRzIHdpdGggbWljcm9iaW9tZSBkYXRhCgpgYGB7cn0KZG9ub3IuaW1tdW5lICU+JSAKZ3JvdXBfYnkocnhfY29kZSkgJT4lCnN1bW1hcml6ZShVbmlxdWVfaWRzID0gbl9kaXN0aW5jdChwdWJfaWQpKQpgYGAKCiMjIyBGdXJ0aGVyIGJyZWFrZG93biBvZiBwYXJ0aWNpcGFudHMgcHJvdmlkaW5nIG1pY3JvYmlvdGEgcGVyIGdyb3VwCgpOdW1iZXIgb2YgcGFydGljaXBhbnRzIHdpdGggYSBnaXZlbiBkYXkgb2YgZmlyc3Qgc2FtcGxlLgoKYGBge3J9CnBzTjIgJT4lIHNhbXBsZV9kYXRhICU+JSBkYXRhLmZyYW1lICU+JSBncm91cF9ieShtdVZpc2l0KSAlPiUgc3VtbWFyaXplKG4gPSBsZW5ndGgobXVWaXNpdCkpCmBgYAoKSG93IG1hbnkgZG9ub3JzIHdlcmUgdGhlcmUgZnJvbSBlYWNoIHRyZWF0bWVudD8KCmBgYHtyfQpwc04yICU+JSBzYW1wbGVfZGF0YSAlPiUgZGF0YS5mcmFtZSAlPiUgZ3JvdXBfYnkocnhfY29kZSkgJT4lIHN1bW1hcml6ZShuID0gbGVuZ3RoKG11VmlzaXQpKQpgYGAKCkJyZWFrZG93biBieSBib3RoIHZpc2l0IGFuZCB0cmVhdG1lbnQKCmBgYHtyfQpzYW1wbGVCcmVha2Rvd24gPC0gcHNOMiAlPiUgc2FtcGxlX2RhdGEgJT4lIGRhdGEuZnJhbWUgJT4lIGdyb3VwX2J5KG11VmlzaXQsIHJ4X2NvZGUpICU+JSBzdW1tYXJpemUobiA9IGxlbmd0aChtdVZpc2l0KSkgJT4lIHNwcmVhZChrZXkgPSByeF9jb2RlLCB2YWx1ZSA9IG4sIGZpbGwgPSAwKSAlPiUgbXV0YXRlKHRvdGFsID0gVDEgKyBUMiArIFQzICsgVDQpCgpzYmNzIDwtIGNvbFN1bXMoc2FtcGxlQnJlYWtkb3duKQoKc2FtcGxlQnJlYWtkb3duIDwtIGJpbmRfcm93cyhzYW1wbGVCcmVha2Rvd24sIHNiY3MpCgpzYW1wbGVCcmVha2Rvd24gJT4lCmthYmxlKCJodG1sIiwgZXNjYXBlID0gRiwgZGlnaXRzID0gMywgYWxpZ24gPSAnYycpICU+JQoja2FibGVfc3R5bGluZygic3RyaXBlZCIsICJob3ZlciIsIGZ1bGxfd2lkdGggPSBGKSAlPiUKI2NvbGxhcHNlX3Jvd3MoY29sdW1ucyA9IDE6MiwgbGF0ZXhfaGxpbmUgPSAiZnVsbCIpICU+JQpwYXNzLT4gc2FtcGxlQnJlYWtkb3duLmh0bWwKYGBgCgpgYGB7cn0Kc2FtcGxlQnJlYWtkb3duLmh0bWwKYGBgCgoKYGBge3J9CgpzYW1wbGVCcmVha2Rvd24uaHRtbCAlPiUgY2F0KGZpbGUgPSAndGFibGVzL3NhbXBsZUJyZWFrZG93bi5odG1sJykKYGBgCgojIFdlaWdodGVkIFVuaWZyYWMKCiMjIFZhcmlhYmlsaXR5IHdpdGhpbiB2cyBiZXR3ZWVuIHBhcnRpY2lwYW50cwoKYGBge3J9CnBzTjJBLnd1ZiA8LSBwaHlsb3NlcTo6ZGlzdGFuY2UocHNOMkEsIG1ldGhvZCA9ICJ3dW5pZnJhYyIpCmBgYAoKYGBge3J9CnBzTjJBICU+JSBzYW1wbGVfZGF0YSAlPiUgLlsxOjUsIDE6MTBdCmBgYAoKYGBge3J9CnBzTjJBICU+JSBzYW1wbGVfZGF0YSAlPiUgYXMoImRhdGEuZnJhbWUiKSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJTYW1wbGUiKSAlPiUgZHBseXI6OnNlbGVjdChTYW1wbGUsIHB1Yl9pZCkgJT4lCnBhc3MgLT4gUzJQCmBgYAoKYGBge3J9CkFsbC5lcXVhbCA8LSBWZWN0b3JpemUoZnVuY3Rpb24oeCx5KXt4ID09IHl9KQpgYGAKCmBgYHtyfQojIyBDb252ZXJ0IGRpc3RhbmNlIG1hdHJpeCBpbnRvIGxvbmcgZm9ybSBtYXRyaXgKcHNOMkEud3VmICU+JSBhcy5tYXRyaXggJT4lIGFzLmRhdGEuZnJhbWUgJT4lCnJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiU2FtcGxlWCIpJT4lCmdhdGhlcihrZXkgPSAiU2FtcGxlWSIsIHZhbHVlID0gIld1ZkRpc3QiLCAtU2FtcGxlWCkgJT4lCmxlZnRfam9pbihTMlAsIGJ5ID0gYygiU2FtcGxlWCIgPSAiU2FtcGxlIikpICU+JSByZW5hbWUocHViX2lkX3ggPSBwdWJfaWQpICU+JQpsZWZ0X2pvaW4oUzJQLCBieSA9IGMoIlNhbXBsZVkiID0gIlNhbXBsZSIpKSAlPiUgcmVuYW1lKHB1Yl9pZF95ID0gcHViX2lkKSAlPiUKbXV0YXRlKFNhbXBsZVggPSBhcy5udW1lcmljKHN0cl9leHRyYWN0KFNhbXBsZVgsICJbMC05XVswLTldIikpKSAlPiUKbXV0YXRlKFNhbXBsZVkgPSBhcy5udW1lcmljKHN0cl9leHRyYWN0KFNhbXBsZVksICJbMC05XVswLTldIikpKSAlPiUKIyMgZGlzY2FyZCBkaWFnb25hbCBkaXNjYXJkIGFuZCB1cHBlciBoYWxmIG9mIHRyaWFuZ3VsYXIgbWF0cml4CmZpbHRlcihTYW1wbGVYIDwgU2FtcGxlWSkgJT4lCm11dGF0ZShpc1NhbWVQZXJzb24gPSBBbGwuZXF1YWwocHViX2lkX3gsIHB1Yl9pZF95KSkgJT4lCiMgIyMgZGlzY2FyZCBjYXNlcyB3aGVyZSBwdWJfaWQgaXMgdW5rbm93bgojIGZpbHRlcihpcy5maW5pdGUocHViX2lkX3gpICYgaXMuZmluaXRlKHB1Yl9pZF95KSkKcGFzcyAtPiBBbGxXdWZEaXN0CgpBbGxXdWZEaXN0ICU+JSBoZWFkCmBgYAoKYGBge3J9CiNBbGxXdWZEaXN0ICU+JSBnZ3Bsb3QoYWVzKHggPSBpc1NhbWVQZXJzb24sIHkgPSBXdWZEaXN0KSkgKyBnZW9tX3Zpb2xpbigpICsgZ2VvbV9kb3RwbG90KGJpbmF4aXMgPSAieSIsIHN0YWNrZGlyID0gImNlbnRlciIsIGJpbndpZHRoID0gLjAwNSkKYGBgCgpHZXQgTWVhbiB2YWx1ZXMgZm9yIGJldHdlZW4gcGFydGljaXBhbnQgYW5kIHdpdGhpbiBwYXJ0aWNpcGFudCB3ZWlnaHRlZCB1bmlmcmFjIGRpc3RhbmNlcy4KCmBgYHtyfQpXdWZNZWFucyA8LSBBbGxXdWZEaXN0ICU+JSBncm91cF9ieShpc1NhbWVQZXJzb24pICU+JSBzdW1tYXJpemUobWVhbiA9IG1lYW4oV3VmRGlzdCkpCld1Zk1lYW5zCmBgYAoKYGBge3J9CiNTYW1lQW5kRGlmZiA8LSBkYXRhLmZyYW1lKGNvbXBhcmlzb24gPSBjKCJkaWZmZXJlbnQiLCAic2FtZSIpLCBXdWZNZWFucyRtZWFuKQpTYW1lQW5kRGlmZiA8LSBXdWZNZWFucyAlPiUgc3ByZWFkKGtleSA9IGlzU2FtZVBlcnNvbiwgdmFsdWUgPSBtZWFuKSAlPiUgcmVuYW1lKGJldHdlZW4gPSAnRkFMU0UnLCB3aXRoaW4gPSAnVFJVRScpICU+JSBtdXRhdGUoZGlmZiA9IGJldHdlZW4td2l0aGluKQpTYW1lQW5kRGlmZgojU2FtZUFuZERpZmZbMSwyXSAtIFNhbWVBbmREaWZmWzIsMl0gIyBEaWZmZXJlbmNlIGluIHdlaWdodGVkIHVuaWZyYWMgZGlzc2ltaWxhcml0eSBiZXR3ZWVuIHNhbWUgYW5kIGRpZmZlcmVudCBwYXJ0Y2lwYW50cwpgYGAKCiMjIyBCb290c3RyYXBwZWQgY29uZmlkZW5jZSBpbnRlcnZhbHMKQm9vdHN0cmFwIHNvbWUgY29uZmlkZW5jZSBpbnRlcnZhbHMgb2Ygd2l0aGluIGFuZCBiZXR3ZWVuIHBhcnRpY2lwYW50IHdlaWdodGVkIHVuaWZyYWMgZGlzdGFuY2VzLgoKYGBge3J9CiMgU3BsaXQgdGhlIGRhdGEKU2FtZVBlcnNvbld1ZkRpc3QgPC0gQWxsV3VmRGlzdCAlPiUgZmlsdGVyKGlzU2FtZVBlcnNvbikgIyU+JSBkcGx5cjo6c2VsZWN0KFd1ZkRpc3QpCkRpZmZlcmVudFBlcnNvbld1ZkRpc3QgPC0gQWxsV3VmRGlzdCAlPiUgZmlsdGVyKCFpc1NhbWVQZXJzb24pCnNldC5zZWVkKDMzNCkKCmJvb3RzU2FtZSA8LSByc2FtcGxlOjpib290c3RyYXBzKFNhbWVQZXJzb25XdWZEaXN0LCB0aW1lcyA9IDEwMDAwKQpib290c0RpZmZlcmVudCA8LSByc2FtcGxlOjpib290c3RyYXBzKERpZmZlcmVudFBlcnNvbld1ZkRpc3QsIHRpbWVzID0gMTAwMDApCmBgYAoKYGBge3J9Cm1lYW5fb2ZfYm9vdHN0cmFwIDwtIGZ1bmN0aW9uKHNwbGl0KXsKICAgIGxvY1ZhbHMgPC0gcnNhbXBsZTo6YW5hbHlzaXMoc3BsaXQpJFd1ZkRpc3QKICAgIG1lYW4obG9jVmFscykKfQpgYGAKCmBgYHtyfQpib290X21lYW5zU2FtZSA8LSBib290c1NhbWUgJT4lIG11dGF0ZShtZWFuID0gbWFwX2RibChzcGxpdHMsIG1lYW5fb2ZfYm9vdHN0cmFwKSkgJT4lIGRwbHlyOjpzZWxlY3QobWVhbikKCmJvb3RfbWVhbnNEaWZmZXJlbnQgPC0gYm9vdHNEaWZmZXJlbnQgJT4lIG11dGF0ZShtZWFuID0gbWFwX2RibChzcGxpdHMsIG1lYW5fb2ZfYm9vdHN0cmFwKSkgJT4lIGRwbHlyOjpzZWxlY3QobWVhbikKCmJvb3RfbWVhbnMgPC0gYmluZF9jb2xzKHdpdGhpbiA9IGJvb3RfbWVhbnNTYW1lJG1lYW4sIGJldHdlZW4gPSBib290X21lYW5zRGlmZmVyZW50JG1lYW4pICU+JQptdXRhdGUoaXNEaWZmZXJlbnRCaWdnZXIgPSBiZXR3ZWVuPndpdGhpbiwgCiAgICAgIERpZk1pbnVzU2FtZSA9IGJldHdlZW4gLSB3aXRoaW4pCiNib290X21lYW5zCgoxLSBzdW0oYm9vdF9tZWFucyRpc0RpZmZlcmVudEJpZ2dlcikvbGVuZ3RoKGJvb3RfbWVhbnMkaXNEaWZmZXJlbnRCaWdnZXIpCmBgYAoKVGhlIGFib3ZlIGlzIGEgYm9vdHN0cmFwcGVkIFAgdmFsdWUgdGhhdCB0aGUgdHdvIGFyZSBkaWZmZXJlbnQgZnJvbSBlYWNob3RoZXIuIFBlciBhIGNvbnZlcnNhdGlvbiBJIGhhZCB3aXRoIEtsYXVzIEh1YmVydC4KU3RpbGwgbmVlZCB0byBmaW5kIGEganVzdGlmaWNhdGlvbiB0aGF0IHRoaXMgYXBwcm9hY2ggaXMgbGVnaXQuCgpgYGB7cn0KYm9vdF9tZWFucyAlPiUgZ2dwbG90KGFlcyh4ID0gRGlmTWludXNTYW1lKSkgKyBnZW9tX2hpc3RvZ3JhbSgpCmBgYAoKQSBoaXN0b2dyYW0gb2YgYm9vdHN0cmFwcGVkIGRpZmZlcmVuY2VzIGJldHdlZW4gd2l0aGluIHBhcnRpY2lwYW50IGFuZCBiZXR3ZWVuIHBhcnRpY2lwYW50IG1lYW4gdmFsdWVzLiAKCmBgYHtyfQpxdWFudGlsZShib290X21lYW5zJERpZk1pbnVzU2FtZSwgYygwLjAyNSwgMC41LCAwLjk3NSkpCmBgYAoKOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIG9mIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIHNhbWUgYW5kIGRpZmZlcmVudCBwZXJzb24gbWljcm9iaW90YS4KCiMjIyBQZXJtdXRhdGlvbiBiYXNlZCBQIHZhbHVlcwoKYGBge3J9ClBlcm1XdWZEaXN0IDwtIEFsbFd1ZkRpc3QgJT4lIG1vZGVscjo6cGVybXV0ZSgxMDAwMCwgV3VmRGlzdCkKbWVhbl9vZl9pc19zYW1lIDwtIGZ1bmN0aW9uKGRmKXtkZiAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgZ3JvdXBfYnkoaXNTYW1lUGVyc29uKSAlPiUgc3VtbWFyaXplKG1lYW4oV3VmRGlzdCkpICU+JSBzcHJlYWQoaXNTYW1lUGVyc29uLCBgbWVhbihXdWZEaXN0KWApfQpgYGAKCmBgYHtyfQp0ZXN0IDwtIFBlcm1XdWZEaXN0ICU+JSBwdWxsKHBlcm0pICU+JSAuW1sxXV0gJT4lIGFzLmRhdGEuZnJhbWUKdGVzdCAlPiUgaGVhZAptZWFuX29mX2lzX3NhbWUodGVzdCkKYGBgCgpgYGB7cn0KU2FtZUFuZERpZmYKYGBgCgpgYGB7cn0KcGVybXV0ZWRNZWFucyA8LSBQZXJtV3VmRGlzdCAlPiUgbXV0YXRlKG1lYW52YWxzID0gbWFwKHBlcm0sIG1lYW5fb2ZfaXNfc2FtZSkpICU+JSBkcGx5cjo6c2VsZWN0KG1lYW52YWxzKSAlPiUgdW5uZXN0ICU+JSAKcmVuYW1lKGJldHdlZW4gPSBgRkFMU0VgLCB3aXRoaW4gPSBgVFJVRWApICU+JQptdXRhdGUoZGlmZiA9IGJldHdlZW4gLSB3aXRoaW4sIGFic2RpZiA9IGFicyhkaWZmKSkgJT4lCm11dGF0ZShpc0V4dHJlbWUgPSBhYnNkaWYgPj0gU2FtZUFuZERpZmYkZGlmZiwgaXNFeHRyZW1lMVRhaWwgPSBkaWZmID49IFNhbWVBbmREaWZmJGRpZmYpCiNwZXJtdXRlZE1lYW5zICU+JSBnZ3Bsb3QoYWVzKHggPSBkaWZmKSkgKyBnZW9tX2hpc3RvZ3JhbSgpICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gU2FtZUFuZERpZmYkZGlmZikKYGBgCgpgYGB7cn0KcGVybXV0ZWRNZWFucyAlPiUgZ2dwbG90KGFlcyh4ID0gZGlmZikpICsgZ2VvbV9oaXN0b2dyYW0oKSArIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IFNhbWVBbmREaWZmJGRpZmYpCmBgYAoKRnJhY3Rpb24gb2YgcGVybXV0ZWQgdmFsdWVzIGxlc3MgZXh0cmVtZSB0aGFuIGRpZmZlcmVuY2UgYmV0d2VlbiBzYW1lIGFuZCBkaWZmZXJlbnQuCgpgYGB7cn0KI3Blcm11dGVkTWVhbnMgJT4lIG11dGF0ZShpc0V4dHJlbWUgPSBhYnNkaWYgPj0gU2FtZUFuZERpZmYkZGlmZikKc3VtKHBlcm11dGVkTWVhbnMkaXNFeHRyZW1lKSAvIGxlbmd0aChwZXJtdXRlZE1lYW5zJGlzRXh0cmVtZSkKc3VtKHBlcm11dGVkTWVhbnMkaXNFeHRyZW1lMVRhaWwpIC8gbGVuZ3RoKHBlcm11dGVkTWVhbnMkaXNFeHRyZW1lMVRhaWwpCmBgYAoKVHdvIGFuZCBvbmUgdGFpbGVkIHBlcm11dGVkIHAtdmFsdWVzCgojIyMgUGxvdCBvZiBjb25mZWRlbmNlIGludGVydmFsIGFuZCByYXcgZGF0YQoKYGBge3J9Cm9wdGlvbnMocmVwci5wbG90LndpZHRoPTYsIHJlcHIucGxvdC5oZWlnaHQ9IDYpCmJvb3RfbWVhbnMgICU+JSBkcGx5cjo6c2VsZWN0KHdpdGhpbiwgYmV0d2VlbikgJT4lIGdhdGhlcihrZXkgPSAiY29tcGFyaXNvbiIsIHZhbHVlID0gIld1ZkRpc3QiKSAlPiUgZ2dwbG90KGFlcyh4ID0gY29tcGFyaXNvbiwgeSA9IFd1ZkRpc3QpKSArIApnZW9tX2RvdHBsb3QoZGF0YSA9IEFsbFd1ZkRpc3QgJT4lIG11dGF0ZShpc1NhbWVQZXJzb24yID0gaWZfZWxzZShpc1NhbWVQZXJzb24sICJ3aXRoaW4iLCAiYmV0d2VlbiIpKQogICAgICAgICAgICAgLCBhZXMoeCA9IGlzU2FtZVBlcnNvbjIsIHkgPSBXdWZEaXN0KSwgYmluYXhpcyA9ICJ5Iiwgc3RhY2tkaXIgPSAiY2VudGVyIiwgYmlud2lkdGggPSAuMDEsIGNvbG91ciA9ICJncmF5NDAiLCBmaWxsID0gIndoaXRlIikgKyAKZ2VvbV92aW9saW4oZmlsbCA9IE5BKSArIApnZW9tX3BvaW50KGRhdGEgPSBkYXRhLmZyYW1lKGNvbXBhcmlzb24gPSBjKCJ3aXRoaW4iLCAiYmV0d2VlbiIpLCBXdWZEaXN0ID0gV3VmTWVhbnMkbWVhbiksIGFlcyh4ID0gY29tcGFyaXNvbiwgeSA9IGMoV3VmRGlzdFsyXSwgV3VmRGlzdFsxXSkpLCBzaGFwZSA9IDIyLCBmaWxsID0gImJsYWNrIiwgc2l6ZSA9IDIpKwp0aGVtZV9idygpICsKc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHMgPSBjKCJ3aXRoaW4iLCAiYmV0d2VlbiIpKSArIGxhYnMoeSA9ICJXZWlnaHRlZCBVbmlmcmFjIERpc3RhbmNlIikgIwpnZ3NhdmUoJ2ZpZ3VyZXMvQmV0d2VlblZzV2l0aGluLnBuZycpICAKYGBgCgpGaWd1cmU6IE9wZW4gY2lyY2xlcyByZXByZXNlbnQgd2VpZ2hlZCB1bmlmcmFjIGRpc3RhbmNlcyBhc3NvY2lhdGVkIHdpdGggcGFpcnMgb2Ygc2FtcGxlcyB0YWtlbiBmcm9tIHRoZSBzYW1lIHNldCBvZiBwYXJ0aWNpcGFudHMsIGF0IGRpZmZlcmVudCB0aW1lIHBvaW50cyAoIndpdGhpbiIpLCBhbmQgc2FtcGxlcyB0YWtlbiBmcm9tIGRpZmZlcmVudCBzZXRzIG9mIHBhcnRpY2lwYW50cyAoImJldHdlZW4iKS4gQmxhY2sgc3F1YXJlcyBpbmRpY2F0ZSB0aGUgb2JzZXJ2ZWQgbWVhbiBvZiB0aGUgd2l0aGluIGFuZCBiZXR3ZWVuIHZhbHVlcy4gVmlvbGlucyBpbmRpY2F0ZSBkaXN0cmlidXRpb25zIG9mIGJvb3RzdHJhcHBlZCBtZWFuIHZhbHVlcy4KCiMjIFZhcmlhYmlsaXR5IGF0IGVhcmxpZXN0IHNhbXBsaW5nCgpgYGB7cn0KcHNOMi53dWYgPC0gcGh5bG9zZXE6OmRpc3RhbmNlKHBzTjIsIG1ldGhvZCA9ICJ3dW5pZnJhYyIpCmBgYAoKYGBge3J9CnBzTjIucGNvYSA8LSBjYXBzY2FsZShwc04yLnd1ZiB+IDEpCmBgYAoKYGBge3J9CiMgSG93IG11Y2ggdmFyaWFuY2Ugc2kgZXhwbGFpbmVkIGJ5IGVhY2ggd2VpZ2h0ZWQgdW5pZnJhYyBheGlzCiMgTm90ZSwgdGVuIGF4ZXMgY292ZXIgOTUlIG9mIHRoZSB2YXJpYW5jZS4gCiMgSSdtIG5vdCBnb2luZyB0byBsb29rIGJleW9uZCB0aGF0IGZvciBhbnkgdGVzdC4KZGF0YS5mcmFtZShlaWcgPSBwc04yLnBjb2EkQ0EkZWlnKSAlPiUKcm93bmFtZXNfdG9fY29sdW1uKCdheGlzJykgJT4lCm11dGF0ZShwcm9wb3J0aW9uID0gZWlnL3N1bShlaWcpKSAlPiUKbXV0YXRlKGN1bXVsYXRpdmUgPSBjdW1zdW0ocHJvcG9ydGlvbikpCmBgYAoKYGBge3J9Cm15X2JyZWFrcyA9IGMoMSwgNzUsIDI1MCwgNTAwLCAxMDAwLDIwMDApCnBzTjIgJT4lIG11dGF0ZV9waHlsb3NlcV9zYW1wbGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYzQxID0gZmFjdG9yKG1lZGNvZGVfaGwoSWdHX2dwNDFfTW9udGhfMCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2cxMjAgPSAoSWdHX0Nvbi42LmdwMTIwLkJfTW9udGhfMTIpKSAtPiBwc04yX21vZApwc04yX21vZCU+JQpzYW1wbGVfZGF0YSgpICU+JQpnZ3Bsb3QoYWVzKHggPSBNRFMxLCB5ID0gTURTMikpICsgZ2VvbV9wb2ludChhZXMoZmlsbCA9IG1jNDEpLCBzaXplID0gNSwgc3Ryb2tlID0gMSwgc2hhcGUgPSAyMSkgKwpjb29yZF9maXhlZChzcXJ0KHBzTjIucGNvYSRDQSRlaWdbMl0vcHNOMi5wY29hJENBJGVpZ1sxXSkpICsKdmlyaWRpczo6c2NhbGVfZmlsbF92aXJpZGlzKG5hbWUgPSAnZ3A0MSBCYXNlbGluZScsIGRpcmVjdGlvbiA9IC0xLCBkaXNjcmV0ZSA9IFRSVUUpICsKI3NjYWxlX2NvbG91cl9tYW51YWwobmFtZSA9ICdncDQxIFByaW1hcnknLCB2YWx1ZXMgPSBjKCdibGFjaycsICdncmV5NzAnKSkgKyAKdGhlbWVfYncoKSAtPiB3dWZvcmRfZ3A0MQoKCnBzTjJfbW9kICU+JQpzYW1wbGVfZGF0YSgpICU+JQpnZ3Bsb3QoYWVzKHggPSBNRFMxLCB5ID0gTURTMikpICsgZ2VvbV9wb2ludChhZXMoZmlsbCA9IGxvZzEyMCksIHNpemUgPSA1LCBzdHJva2UgPSAxLCBzaGFwZSA9IDIxKSArCmNvb3JkX2ZpeGVkKHNxcnQocHNOMi5wY29hJENBJGVpZ1syXS9wc04yLnBjb2EkQ0EkZWlnWzFdKSkgKwp2aXJpZGlzOjpzY2FsZV9maWxsX3ZpcmlkaXMobmFtZSA9ICdncDEyMCBGaW5hbCcsIGRpcmVjdGlvbiA9IDEsIHRyYW5zID0gInNxcnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBteV9icmVha3MsIGxhYmVscyA9IG15X2JyZWFrcykgKwojc2NhbGVfY29sb3VyX21hbnVhbChuYW1lID0gJ2dwNDEgUHJpbWFyeScsIHZhbHVlcyA9IGMoJ2JsYWNrJywgJ2dyZXk3MCcpKSArIAp0aGVtZV9idygpIC0+IHd1Zm9yZF9ncDEyMAoKcGFyIDwtIG9wdGlvbnMoKQpvcHRpb25zKHJlcHIucGxvdC53aWR0aD0xMSwgcmVwci5wbG90LmhlaWdodD0gNCkKI2cgPC0gZ3JpZC5hcnJhbmdlKHd1Zm9yZF9ncDQxLCB3dWZvcmRfZ3AxMjAsIG5jb2wgPSAyKQpnIDwtIGNvd3Bsb3Q6OnBsb3RfZ3JpZCh3dWZvcmRfZ3A0MSwgd3Vmb3JkX2dwMTIwLCBuY29sID0gMiwgbGFiZWxzID0gYygiQSIsICJCIiksIGxhYmVsX3NpemUgPSAyNCkKZwojZ2dzYXZlKCdmaWd1cmVzL3d1bmlmcmFjX0FncDQxX0JncDEyMF9wY29hLnBuZycsIGcsIHdpZHRoID0gOCwgaGVpZ2h0ID0gNCkKI2dnc2F2ZSgnZmlndXJlcy93dW5pZnJhY19BZ3A0MV9CZ3AxMjBfcGNvYS5zdmcnLCBnLCB3aWR0aCA9IDgsIGhlaWdodCA9IDQpCmNvd3Bsb3Q6OnNhdmVfcGxvdCgnZmlndXJlcy93dW5pZnJhY19BZ3A0MV9CZ3AxMjBfcGNvYS5wbmcnLCBnLCBiYXNlX3dpZHRoID0gOCwgYmFzZV9oZWlnaHQgPSA0KQpjb3dwbG90OjpzYXZlX3Bsb3QoJ2ZpZ3VyZXMvd3VuaWZyYWNfQWdwNDFfQmdwMTIwX3Bjb2Euc3ZnJywgZywgYmFzZV93aWR0aCA9IDgsIGJhc2VfaGVpZ2h0ID0gNCkKYGBgCgojIEtlcm5lbCBSZWdyZXNzaW9uIGFuZCBXZWlnaHRlZCBVbmlmcmFjIEdMTQoKYGBge3J9Cnd1ZktOMiA8LSBEMksoYXMubWF0cml4KHBzTjIud3VmKSkKYGBgCgpgYGB7cn0KbXVEb25lcnMgPC0gdW5pcXVlKHNhbXBsZV9kYXRhKHBzTjIpJHB1Yl9pZCkKYGBgCgpgYGB7cn0KaW1tdW5lLmRhdGEgJT4lCmZpbHRlcihwdWJfaWQgJWluJSBtdURvbmVycykgJT4lCmZpbHRlcigKICAgICh0eXBlID09ICdJZ0cnICYgCiAgICBhbnRpZ2VuICVpbiUgYW50czEgJgogICAgbW9udGggJWluJSBjKDYuNSwxMikKICAgICkgfAogICAgKHR5cGUgJWluJSBjKCdJZ0cnLCAnSWdBJykgJgogICAgIGFudGlnZW4gJWluJSBhbnRzMiAmCiAgICAgbW9udGggJWluJSBjKDAsNi41LDEyKQogICAgKSB8CiAgICB0eXBlID09ICdDRDQrJyAmCiAgICBhbnRpZ2VuID09ICdBTlkuRU5WLlBURUcnICYKICAgIG1vbnRoICVpbiUgYyg2LjUsIDEyKQogICAgICApLT4gdXNlLmltbXVuZQpoZWFkKHVzZS5pbW11bmUpCmBgYAoKYGBge3J9CiMgRG8gcGVybWFub3ZhIGFuZCByZWxhdGVkIHRlc3RzIHRvIGEgdmFyaWFibGUgb2YgaW50ZXJlc3QKIyBUaGlzIGZ1bmN0aW9uIGlzIHByZXR0eSBzcGVjaWZpYyB0byB0aGlzIGFuYWx5c2lzLCBzbyBJJ20gZ29pbmcgdG8gbGVhdmUgaXQKIyBoZXJlIGluIHRoZSBub3RlYm9vayBmaWxlCkNhcFZhciA8LSBmdW5jdGlvbih4LCBucGVybSA9IDk5OTksIHRyYW5zZm9ybWF0aW9uID0gbWVkY29kZTIsIGZhbWlseSA9ICdiaW5vbWlhbCcpewogICAgIyMgUHVsbCBvdXQgdGhlIG5lZWRlZCBkYXRhCiAgICAKICAgIHBzTjIud01EUyA8LSBwc04yICU+JSBwaHlsb19qb2luKHNjb3Jlcyhwc04yLnBjb2EsIGRpc3BsYXkgPSAic2l0ZXMiLCBjaG9pY2VzID0gMToxMCkgJT4lCiAgICAgICAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSAlPiUgcm93bmFtZXNfdG9fY29sdW1uLCBieSA9ICdyb3duYW1lJykKICAgIAogICAgbWVkV3VmIDwtIE5BCiAgICByYW5rV3VmIDwtIE5BCiAgICBsb2NQUyA8LSBwaHlsb19qb2luKHBzTjIud01EUywgeCwgYnkgPSAncHViX2lkJykgCiAgICB5ZGF0YTAgPC0gc2FtcGxlX2RhdGEobG9jUFMpJG1hZwogICAgeW5hIDwtIGlzLm5hKHlkYXRhMCkKICAgICNsb2Mud3VmIDwtIHd1ZktOMgogICAgI2xvYy5qc2QgPC0ganNkS04yCiAgICB5ZGF0YSA8LSB5ZGF0YTAKICAgIAogICAgeWRhdGEgPC0geWRhdGEwWyF5bmFdCiAgICBsb2Mud3VmMiA8LSBwc04yLnd1ZiAlPiUgYXMubWF0cml4ICU+JSAuWyF5bmEsICF5bmFdCiAgICAKICAgIG1lZFd1ZiA8LSBhZG9uaXMobG9jLnd1ZjIgfiB0cmFuc2Zvcm1hdGlvbih5ZGF0YSksIHBlcm11dGF0aW9ucyA9IG5wZXJtKQogICAgI21lZFd1ZiRhb3YudGFiWzEsYygnUjInLCAnUHIoPkYpJyldCiAgICAKICAgICMjIENhcHNjYWxlIHJldHVybnMgdGhlIHNhbWUgcmVzdWx0cyBhcyBhZG9uaXMgKHBlcm1hbm92YSksIGJ1dCBhbHNvIGdpdmVzIHNvbWUgb3RoZXIgaW50ZXJlc3RpbmcgcmVzdWx0cwogICAgCiAgICBtZWRXdWZDYXAgPC0gY2Fwc2NhbGUobG9jLnd1ZjIgfiB0cmFuc2Zvcm1hdGlvbih5ZGF0YSkpCiAgICBjYXBhbm92YSA8LSBhbm92YShtZWRXdWZDYXAsIHBlcm11dGF0aW9ucyA9IG5wZXJtKQogICAgCiAgICBzYW1EZiA8LSBsb2NQUyAlPiUgc2FtcGxlX2RhdGEgJT4lIGFzKCdkYXRhLmZyYW1lJykgJT4lIHJvd25hbWVzX3RvX2NvbHVtbiAlPiUKICAgICBsZWZ0X2pvaW4oCiAgICAgICAgdmVnYW46OnNjb3JlcyhtZWRXdWZDYXAsIGRpc3BsYXkgPSAnc2l0ZXMnKSAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgZHBseXI6OnNlbGVjdChDQVAxKSAlPiUKICAgICAgICByb3duYW1lc190b19jb2x1bW4sIGJ5ID0gJ3Jvd25hbWUnKSAlPiUgLlsheW5hLF0KICAgIAojICAgICAjIElzIGdpdmluZyBvbmx5IHBvc2l0aXZlIHJlc3VsdHMgd2l0aCBDQVAxLCBub3Qgc3VyZSB3aHkKIyAgICAgZ2xtQW5vdmEgPC0gZ2xtKG1lZGNvZGUoeWRhdGEpIH4gIE1EUzEgKyBDQVAxLCBkYXRhID0gc2FtRGYsIGZhbWlseSA9ICdiaW5vbWlhbCcpICU+JSBhbm92YSh0ZXN0ID0gIkNoaXNxIikKICAgIGxvY19nbG0gPC0gZ2xtKHRyYW5zZm9ybWF0aW9uKHlkYXRhKSB+ICBNRFMxLCBkYXRhID0gc2FtRGYsIGZhbWlseSA9IGZhbWlseSkKICAgIGdsbUFub3ZhIDwtIGxvY19nbG0gJT4lIGFub3ZhKHRlc3QgPSAiQ2hpc3EiKQogICAgI2dsbUFub3ZhWydDQVAxJywgJ0RldmlhbmNlJ10vb3V0X2NhcGFub3ZhWydOVUxMJywgJ1Jlc2lkLiBEZXYnXQogICAgCiAgICAjIyBjaGVjayBhZ2FpbnN0IG1pcmthdAogICAgbG9jLkt3dWYyIDwtIHd1ZktOMlsheW5hLCAheW5hXQogICAgbWlya2F0UCA8LSBNaVJLQVQoeSA9IHRyYW5zZm9ybWF0aW9uKHlkYXRhKSwgS3MgPSBsb2MuS3d1ZjIsIG91dF90eXBlID0gIkMiLCBtZXRob2QgPSAncGVybXV0YXRpb24nLCBucGVybSA9IG5wZXJtKQogICAgCiAgICAjbGlzdChtZWRXdWYsIGNhcGFub3ZhLCBtaXJrYXRQKQogICAgCiAgICBwcmVkX3BjdCA8LSBwcmVkaWN0KGxvY19nbG0sIHR5cGUgPSAicmVzcG9uc2UiKQogICAgcHJlZF8wMSA8LSBhcy5udW1lcmljKHByZWRpY3QobG9jX2dsbSwgdHlwZSA9ICJyZXNwb25zZSIpID4gMC41KQogICAgCiAgICBhY2N1cmFjeSA8LSBtZWFuKHRyYW5zZm9ybWF0aW9uKHlkYXRhKSA9PSBwcmVkXzAxKQogICAgCiAgICAgICAgbnVsbF9nbG0gPC0gdXBkYXRlKGxvY19nbG0sIH4xKQoKICAgICMgQ2Fub25pY2FsIGNhbHVjbGF0aW9uIG9mIE1jRmFkZGVuJ3MgUjIgZm9yIHRoZSBHTE0KICAgIE1jRmFkZGVuID0gMS0gKGxvZ0xpayhsb2NfZ2xtKS8gbG9nTGlrKG51bGxfZ2xtKSkKICAgIEwuZnVsbCA9IGxvZ0xpayhsb2NfZ2xtKQogICAgRC5mdWxsID0gLTIgKiBMLmZ1bGwKICAgIEwuYmFzZSA9IGxvZ0xpayhudWxsX2dsbSkKICAgIEQuYmFzZSA9IC0yICogTC5iYXNlCiAgICBuID0gZGltKHNhbURmKVsxXQogICAgTmFnZWxrZXJrZSA9ICgxIC0gZXhwKChELmZ1bGwgLSBELmJhc2UpL24pKS8oMSAtIGV4cCgtRC5iYXNlL24pKQogICAgCiAgICAKICAgICMgQSBHTE0gb2YgYWxsIHdlaWdodGVkIHVuaWZyYWMgY29tcG9uZW50cwogICAgCiAgICAKICAgIGRhdGEuZnJhbWUoCiAgICAgICAgY2Fwcy5QID0gY2FwYW5vdmFbJ01vZGVsJywgJ1ByKD5GKSddLAogICAgICAgIGFkb25pc1AgPSBtZWRXdWYkYW92LnRhYlsxLCAnUHIoPkYpJ10sCiAgICAgICAgbWlyLlAgPSBtaXJrYXRQLAogICAgICAgIGNhcHMuRiA9IGNhcGFub3ZhWydNb2RlbCcsICdGJ10sCiAgICAgICAgY2Fwcy5SMiA9IG1lZFd1ZkNhcCRDQ0EkdG90LmNoaS9tZWRXdWZDYXAkdG90LmNoaSwgCiAgICAgICAgd3VmMS5QID0gZ2xtQW5vdmFbJ01EUzEnLCAnUHIoPkNoaSknXSwKICAgICAgICB3dWYxLkRSID0gZ2xtQW5vdmFbJ01EUzEnLCAnRGV2aWFuY2UnXSAvIGdsbUFub3ZhWydOVUxMJywgJ1Jlc2lkLiBEZXYnXSwKICAgICAgICB3dWYxLk1jRmFkZGVuID0gTmFnZWxrZXJrZSwKICAgICAgICBhY2N1cmFjeSwKICAgICAgICB3dWYxLmNvZWYgPSBjb2VmKGxvY19nbG0pWzJdCiAgICAgICAgI2NhcDEuUCA9IGdsbUFub3ZhWydDQVAxJywgJ1ByKD5DaGkpJ10sCiAgICAgICAgI2NhcDEuUjIgPSBnbG1Bbm92YVsnQ0FQMScsICdEZXZpYW5jZSddIC8gZ2xtQW5vdmFbJ05VTEwnLCAnUmVzaWQuIERldiddCiAgICApCiAgICB9CiAgICAKYGBgCgpgYGB7cn0KdXNlLmltbXVuZSAlPiUKZmlsdGVyKHR5cGUgPT0gJ0lnRycgJiBhbnRpZ2VuID09ICdncDQxJyYgbW9udGggPT0gMCAmIGN0ID09ICdUJykgLT4gdGVzdC5pbW11bmUxCmBgYAoKYGBge3J9CiMgSnVzdCBjb25maXJtaW5nIHRoYXQgdGhlIGZ1bmN0aW9uIHdvcmtzIGJlZm9yZSBpdCBnb2VzIGluIGEgZ2lhbnQgbG9vcC4gSSdkIGRlbGV0ZSB0aGlzLAojIGJ1dCBpJ2xsIGp1c3QgZW5kIHVwIG5lZWRpbmcgaXQgYWdhaW4gaWYgSSBkby4KcHRtID0gcHJvYy50aW1lKCkKdHBzIDwtIENhcFZhcih0ZXN0LmltbXVuZTEsIG5wZXJtID0gOTk5OSwgdHJhbnNmb3JtYXRpb24gPSBtZWRjb2RlLCBmYW1pbHkgPSAnYmlub21pYWwnKQpwcm9jLnRpbWUoKSAtIHB0bQp0cHMKYGBgCgpgYGB7cn0KdXNlLmltbXVuZSAlPiUKZmlsdGVyKHR5cGUgPT0gJ0NENCsnICYgbW9udGggPT0gNi41ICYgY3QgPT0gJ1QnKSAtPiB0ZXN0LmltbXVuZS5wdGVnCmBgYAoKYGBge3J9CiMgSnVzdCBjb25maXJtaW5nIHRoYXQgdGhlIGZ1bmN0aW9uIHdvcmtzIGJlZm9yZSBpdCBnb2VzIGluIGEgZ2lhbnQgbG9vcC4gSSdkIGRlbGV0ZSB0aGlzLAojIGJ1dCBpJ2xsIGp1c3QgZW5kIHVwIG5lZWRpbmcgaXQgYWdhaW4gaWYgSSBkby4KcHRtID0gcHJvYy50aW1lKCkKdHBzIDwtIENhcFZhcih0ZXN0LmltbXVuZS5wdGVnLCBucGVybSA9IGpucGVybSwgdHJhbnNmb3JtYXRpb24gPSBtZWRjb2RlLCBmYW1pbHkgPSAnYmlub21pYWwnKQpwcm9jLnRpbWUoKSAtIHB0bQp0cHMKYGBgCgpgYGB7cn0KIyBSdW4gYWJvdmUgZnVuY3Rpb24gYWdhaW5zdCBldmVyeSByZWxldmFudCB2YXJpYWJsZS4KcHRtIDwtIHByb2MudGltZSgpCgp1c2UuaW1tdW5lICU+JSAKZmlsdGVyKGN0ID09ICdUJykgJT4lCmdyb3VwX2J5KHR5cGUsIGFudGlnZW4sIG1vbnRoKSAlPiUKZG8oZGF0YS5mcmFtZShDYXBWYXIoLiwgbnBlcm0gPSBqbnBlcm0pKSkgLT4gcGVybUtlcm5UYWJsZQpwZXJtS2VyblRhYmxlCgpwcm9jLnRpbWUoKSAtIHB0bQpgYGAKClRoZSBhYm92ZSBmdW5jdGlvbiBydW5zIHNldmVyYWwgZXh0cmEgdGVzdHMuIFJlc3VsdHMgYXMgZm9sbG93czoKCnR5cGUgYW50aWdlbiB2aXNpdG5vIC0gdGhpbmdzIHdlIHJ1biBvdmVyCgpjYXBzLlAgLSBDYXBzY2FsZSB0ZXN0IGFza3Mgd2hldGhlciBpZiB3ZSByb3RhdGUgdGhpbmdzIGEgYml0IGFuZCB0aGVuIHRyeSB0byB1c2UgdGhlIGJlc3QgYXhpcyB0byBjb21wYXJlIHRvIHRoZSBkYXRhLiBJdHMgc2ltaWxhciB0byB0aGUgd3VmMS5QIHZhbHVlLCBidXQgd2l0aCBzb21lIHJvdGF0aW9uCgphZG9uaXNQIC0gcC12YWx1ZSBmb3IgYSBwZXJtYW5vdmEgdGVzdC4gU2ltaWxhciB0byBtaXJrYXQgcC12YWx1ZS4gT25lIGtleSBleGNlcHRpb24gaXMgdGhhdCBpZ2dfZ3A0MV9Nb250aF8wIGZhbGxzIG9uIGRpZmZlcmVudCBzaWRlcyBvZiB0aGUgMC4wNSB0aHJlc2hvbGQuCgptaXIuUCBpcyB0aGUgcCB2YWx1ZSBmb3IgdGhlIGtlcm5lbCByZWdyZXNzaW9uIHRlc3QsIGFzIHJ1biBpbiB0aGUgTWlSS0FUIHBhY2thZ2UuIAooWmhhbyBldCBhbC4sIDIwMTUpCgpjYXBzLkYgYW5kIGNhcHMgUjIgYXJlIHRoZSBmIGFuZCByIHNxdWFyZWQgdmFsdWVzIGZvciB0aGUgY2Fwc2NhbGUgdGVzdC4KCnd1Zi5QIC0gaXMgdGhlIHAgdmFsdWUgb2YgYSBnbG0gY29tcGFyaW5nIHdlaWdodGVkIHVuaWZyYWMgY29tcG9uZW50IG9uZSBhZ2FpbnN0IHZhcmlhYmxlcyBvZiBpbnRlcmVzdC4gVGhpcyB0ZXN0IGFwcGVhcnMgdG8gYWx3YXlzIGJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnRseSBwb3NpdGl2ZSB3aGVuIHRoZSBtaXJrYXQgdGVzdCBpcyBwb3NpdHZlLgoKd3VmMS5EUiAtIG9uZSB3YXkgb2YgY2FsY3VsYXRpbmcgYW4gUjIgdmFsdWUgZnJvbSBhIGdsbS4gV2UgZGV2aWRlIHRoZSBkZXZpYW5jZSBieSB0aGUgcmVzaWR1YWwgZGV2aWFuY2UKCnd1ZjEuTWNGYWRkZW4gLSBpcyBhIE1jRmFkZGVuJ3MgcHNldWRvIFJeMi4gVGhpcyB0dXJucyBvdXQgdG8gYmUgaWRlbnRpY2FsIHRvIHRoZSBwcmV2aW91cyBjYWxjdWxhdGlvbi4KCmFjY3VyYWN5IC0gdGhlIGZyYWN0aW9uIG9mIHRoZSB0aW1lIHRoYXQgdGhlIGdsbSBwcmVkaWN0cyBzb21ldGhpbmcgZmFsbHMgYWJvdmUgb3IgYmVsb3cgdGhlIG1lZGlhbiBjb3JyZWN0bHkuIFRoaXMgdHVybnMgb3V0IHRvIG5vdCBiZSBzdXBlciBpbmZvcm1hdGl2ZS4gRXZlcnl0aGluZyBoYXMgYXJvdW5kIGEgNjAlIGFjY3VyYWN5LgoKd3VmMS5jb2VmIC0gdGhlIGNvZWZmaWNpZW50IG9mIHRoZSBnbG0gbW9kZWwuIFRoZSBzaWduIGlzIHJlbGV2YW50LiBUaGluZ3Mgd2l0aCBwb3N0aXZlIHNpZ24gYXJlIGFzc29jaWF0ZWQgd2l0aCBoaWdoIHZhbHVlcyBvZiB3ZWlnaHRlZCB1bmlmcmFjIGF4aXMgMS4KCmBgYHtyfQojIENsZWFuIHVwIHNvIHdlIGp1c3Qgc2VlIHRoZSByZXN1bHRzIG9mIHRoZSBrZXJuZWwgcmVncmVzc2lvbiAKY29uY2lzZVBlcm1LZXJuVGFibGUgPC0gcGVybUtlcm5UYWJsZSAlPiUgdW5ncm91cCAlPiUKbXV0YXRlKEtlcm5lbF9RID0gcDJxKG1pci5QKSwgTURTMV9RID0gcDJxKHd1ZjEuUCkpICU+JQpkcGx5cjo6c2VsZWN0KFR5cGUgPSB0eXBlLCBBbnRpZ2VuID0gYW50aWdlbixNb250aCA9IG1vbnRoLCBLZXJuZWxfUCA9IG1pci5QLCBLZXJuZWxfUSwKICAgICAgICAgICAgICBNRFMxX1AgPSB3dWYxLlAsIE1EUzFfUSwgR2xtTURTMV9SMiA9IHd1ZjEuTWNGYWRkZW4sIE1EUzFfQ29lZiA9IHd1ZjEuY29lZikgJT4lCmFzLmRhdGEuZnJhbWUgJT4lIApwYXNzIC0+IGNvbmNpc2VQZXJtS2VyblRhYmxlCndyaXRlLmNzdihmb3JtYXQoY29uY2lzZVBlcm1LZXJuVGFibGUsIGRpZ2l0cyA9IDMpLCAndGFibGVzL2NvbmNpc2VQZXJta2VyblRhYmxlLmNzdicpCmBgYAoKIyMjIFRhYmxlIDEKCmBgYHtyfQojIGV4cG9ydCBjb25kaXRpb25hbGx5IGZvcm1hdHRlZCB0YWJsZSBhcyBodG1sCgpjb2xOYW1lczEgPSBjKCcgJyA9IDMsICdLZXJuZWwnID0gMiwgJ01EUycgPSA0KQpjb2xOYW1lczIgPSBjKCdUeXBlJywgJ0FudGlnZW4nLCAnTW9udGgnLCAnUCcsICdRJywgJ1AnLCAnUScsICdSMicsICdDb2VmJyApCgpjb25jaXNlUGVybUtlcm5UYWJsZSAlPiUKbXV0YXRlKAogICAgIyB0aGlzIHJvdyBuZWVkcyB0byBoYXBwZW4gZmlyc3QsIHNpbmNlIHRoZSByZWZvcm1hdHRpbmcgb2YgdGhlIG5vdGhlciBudW1iZXJzIG1ha2VzIHRoZW0gaGFyZGVyIHRvIGNhbGwKICAgIE1EUzFfQ29lZiA9IGNlbGxfc3BlYyhmb3JtYXQoTURTMV9Db2VmLCBkaWdpdHMgPSAzKSwgImh0bWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKEtlcm5lbF9QIDwgMC4wNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBpdGFsaWMgPSBpZmVsc2UoS2VybmVsX1AgPCAwLjA1ICYgTURTMV9Db2VmIDwgMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IGlmZWxzZShLZXJuZWxfUCA8IDAuMDUsIGlmZWxzZShNRFMxX0NvZWYgPCAwLCAibGlnaHRzYWxtb24iLCAibGlnaHRibHVlIiksICIiKQogICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgIEtlcm5lbF9QID0gY2VsbF9zcGVjKGZvcm1hdF9yb3VuZChLZXJuZWxfUCwgMyksICJodG1sIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvbGQgPSBpZmVsc2UoS2VybmVsX1AgPCAwLjA1LCBULCBGKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBpZmVsc2UoS2VybmVsX1AgPCAwLjA1LCAneWVsbG93JywgJycpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICBLZXJuZWxfUSA9IGNlbGxfc3BlYyhmb3JtYXRfcm91bmQoS2VybmVsX1EsIDMpLCAiaHRtbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKEtlcm5lbF9RIDwgMC4yLCBULCBGKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBpZmVsc2UoS2VybmVsX1EgPCAwLjIsICdsaWdodHllbGxvdycsICcnKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgIE1EUzFfUCAgPSBjZWxsX3NwZWMoZm9ybWF0X3JvdW5kKE1EUzFfUCwgMyksICJodG1sIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvbGQgPSBpZmVsc2UoTURTMV9QIDwgMC4wNSwgVCwgRiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gaWZlbHNlKE1EUzFfUCA8IDAuMDUsICd5ZWxsb3cnLCAnJykKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgIE1EUzFfUSA9IGNlbGxfc3BlYyhmb3JtYXRfcm91bmQoTURTMV9RLCAzKSwgImh0bWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9sZCA9IGlmZWxzZShNRFMxX1EgPCAwLjIsIFQsIEYpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IGlmZWxzZShNRFMxX1EgPCAwLjIsICdsaWdodHllbGxvdycsICcnKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgI01vbnRoID0gY2VsbF9zcGVjKGZvcm1hdF9yb3VuZChNb250aCwwKSwgImh0bWwiKQogICAgTW9udGggPSBjZWxsX3NwZWMoTW9udGgsICJodG1sIikKCiAgICAKICAgICAgKSAlPiUKbXV0YXRlKEFudGlnZW4gPSBnc3ViKCdBTlkuRU5WLlBURUcnLCAnQW55IEVOViBQVEVHJywgQW50aWdlbikpICU+JQptdXRhdGUoQW50aWdlbiA9IGdzdWIoJ2dwNzBfQi5DYXNlQV9WMV9WMicsICdncDcwIEIuQ2FzZUEgVjEtVjInLCBBbnRpZ2VuKSkgLT4gdG9UYWJsZQpgYGAKCmBgYHtyfQoKdG9UYWJsZSAlPiUKCmthYmxlKCJodG1sIiwgZXNjYXBlID0gRiwgZGlnaXRzID0gMywgYWxpZ24gPSAnYycsIGNvbC5uYW1lcyA9IGNvbE5hbWVzMikgJT4lCmthYmxlX3N0eWxpbmcoInN0cmlwZWQiLCAiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikgJT4lCmFkZF9oZWFkZXJfYWJvdmUoY29sTmFtZXMxKSAlPiUKY29sbGFwc2Vfcm93cyhjb2x1bW5zID0gMToyLCBsYXRleF9obGluZSA9ICJmdWxsIikgLT4gY29uY2lzZVBlcm1LZXJuVGFibGUuaHRtbAoKY29uY2lzZVBlcm1LZXJuVGFibGUuaHRtbAoKY29uY2lzZVBlcm1LZXJuVGFibGUuaHRtbCAlPiUgY2F0KGZpbGUgPSAndGFibGVzL2NvbmNpc2VQZXJta2VyblRhYmxlLmh0bWwnKQpgYGAKCkxhdGV4IHZlcnNpb24gb2YgdGhlIHNhbWUgdGFibGUKCmBgYHtyfQoKZG9jSGVhZCA8LSAiXFxkb2N1bWVudGNsYXNzWzEycHRde2FydGljbGV9ICUgdXNlIGxhcmdlciB0eXBlOyBkZWZhdWx0IHdvdWxkIGJlIDEwcHQKClxcdXNlcGFja2FnZVt1dGY4XXtpbnB1dGVuY30gJSBzZXQgaW5wdXQgZW5jb2RpbmcgKG5vdCBuZWVkZWQgd2l0aCBYZUxhVGVYKQpcXHVzZXBhY2thZ2V7Ym9va3RhYnN9ClxcdXNlcGFja2FnZXtsb25ndGFibGV9ClxcdXNlcGFja2FnZXthcnJheX0KXFx1c2VwYWNrYWdle211bHRpcm93fQpcXHVzZXBhY2thZ2VbdGFibGVde3hjb2xvcn0KXFx1c2VwYWNrYWdle3dyYXBmaWd9ClxcdXNlcGFja2FnZXtmbG9hdH0KXFx1c2VwYWNrYWdle2NvbG9ydGJsfQpcXHVzZXBhY2thZ2V7cGRmbHNjYXBlfQpcXHVzZXBhY2thZ2V7dGFidX0KXFx1c2VwYWNrYWdle3RocmVlcGFydHRhYmxlfQpcXHVzZXBhY2thZ2V7dGhyZWVwYXJ0dGFibGV4fQpcXHVzZXBhY2thZ2Vbbm9ybWFsZW1de3VsZW19ClxcdXNlcGFja2FnZXttYWtlY2VsbH0KClxcZGVmaW5lY29sb3J7Z3JlZW59e3JnYn17MSwgMSwgLjl9CgpcXGJlZ2lue2RvY3VtZW50fQoiCgpkb2NUYWlsIDwtICJcXGVuZHtkb2N1bWVudH0KIgpgYGAKCmBgYHtyfQojIE1ha2UgbGF0ZXggdGFibGUKCmNvbmNpc2VQZXJtS2VyblRhYmxlICU+JQptdXRhdGUoCiAgICAjIHRoaXMgcm93IG5lZWRzIHRvIGhhcHBlbiBmaXJzdCwgc2luY2UgdGhlIHJlZm9ybWF0dGluZyBvZiB0aGUgbm90aGVyIG51bWJlcnMgbWFrZXMgdGhlbSBoYXJkZXIgdG8gY2FsbAogICAgTURTMV9Db2VmID0gY2VsbF9zcGVjKGZvcm1hdChNRFMxX0NvZWYsIGRpZ2l0cyA9IDMpLCAibGF0ZXgiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKE1EUzFfUCA8IDAuMDUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgaXRhbGljID0gaWZlbHNlKE1EUzFfUCA8IDAuMDUgJiBNRFMxX0NvZWYgPCAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGKSksCiAgICBLZXJuZWxfUCA9IGNlbGxfc3BlYyhmb3JtYXRfcm91bmQoS2VybmVsX1AsIDMpLCAibGF0ZXgiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9sZCA9IGlmZWxzZShLZXJuZWxfUCA8IDAuMDUsIFQsIEYpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IGlmZWxzZShLZXJuZWxfUCA8IDAuMDUsICd5ZWxsb3cnLCAnd2hpdGUnKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgS2VybmVsX1EgPSBjZWxsX3NwZWMoZm9ybWF0X3JvdW5kKEtlcm5lbF9RLCAzKSwgImxhdGV4IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvbGQgPSBpZmVsc2UoS2VybmVsX1EgPCAwLjIsIFQsIEYpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IGlmZWxzZShLZXJuZWxfUSA8IDAuMiwgJ2dyZWVuJywgJ3doaXRlJykKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICBNRFMxX1AgID0gY2VsbF9zcGVjKGZvcm1hdF9yb3VuZChNRFMxX1AsIDMpLCAibGF0ZXgiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9sZCA9IGlmZWxzZShNRFMxX1AgPCAwLjA1LCBULCBGKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBpZmVsc2UoTURTMV9QIDwgMC4wNSwgJ3llbGxvdycsICd3aGl0ZScpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICBNRFMxX1EgPSBjZWxsX3NwZWMoZm9ybWF0X3JvdW5kKE1EUzFfUSwgMyksICJsYXRleCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKE1EUzFfUSA8IDAuMiwgVCwgRiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gaWZlbHNlKE1EUzFfUSA8IDAuMiwgJ2dyZWVuJywgJ3doaXRlJykKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICNNb250aCA9IGNlbGxfc3BlYyhmb3JtYXRfcm91bmQoTW9udGgsMCksICJodG1sIikKICAgIE1vbnRoID0gY2VsbF9zcGVjKE1vbnRoLCAibGF0ZXgiKQoKICAgIAogICAgICApICU+JQptdXRhdGUoQW50aWdlbiA9IGdzdWIoJ0FOWS5FTlYuUFRFRycsICdBbnkgRU5WIFBURUcnLCBBbnRpZ2VuKSkgJT4lCm11dGF0ZShBbnRpZ2VuID0gZ3N1YignZ3A3MF9CLkNhc2VBX1YxX1YyJywgJ2dwNzAgQi5DYXNlQSBWMS1WMicsIEFudGlnZW4pKSAtPiB0b1RhYmxlCgp0b1RhYmxlICU+JSAKa2FibGUoImxhdGV4IiwgZXNjYXBlID0gRiwgZGlnaXRzID0gMywgYWxpZ24gPSAnYycsIGNvbC5uYW1lcyA9IGNvbE5hbWVzMiwgYm9va3RhYnMgPSBUKSAlPiUKa2FibGVfc3R5bGluZyhwb3NpdGlvbiA9ICJsZWZ0IikgJT4lCgphZGRfaGVhZGVyX2Fib3ZlKGNvbE5hbWVzMSkgJT4lCmNvbGxhcHNlX3Jvd3MoY29sdW1ucyA9IDE6MiwgbGF0ZXhfaGxpbmUgPSAiZnVsbCIpICU+JQpwYXNzIC0+IGNvbmNpc2VQZXJtS2VyblRhYmxlLnRleApgYGAKCmBgYHtyfQojIFByaW50IGxhdGV4IHRhYmxlIHRvIHRleCBmaWxlCgpjYXQoZG9jSGVhZCwgY29uY2lzZVBlcm1LZXJuVGFibGUudGV4LCBkb2NUYWlsLCBmaWxlID0gJ3RhYmxlcy9jb25jaXNlUGVybWtlcm5UYWJsZTEudGV4JykKYGBgCgpgYGB7cn0KY29uY2lzZVBlcm1LZXJuVGFibGUgJT4lIGZpbHRlcihLZXJuZWxfUCA8IDAuMDUpIC0+IHNob3J0UGVybWtlcm5UYWJsZQpzaG9ydFBlcm1rZXJuVGFibGUKd3JpdGUuY3N2KGZvcm1hdChzaG9ydFBlcm1rZXJuVGFibGUsIGRpZ2l0cyA9IDMpLCAndGFibGVzL3Nob3J0UGVybWtlcm5UYWJsZS5jc3YnKQpgYGAKCiMjIyBRLVEgUGxvdAoKT2Yga2VybmVsIHJlZ3Jlc3Npb24gUCB2YWx1ZXMKCmBgYHtyfQpteV9ydW5pZiA8LSBmdW5jdGlvbihMZW4pewogICAgbG9jX3J1bmlmIDwtIHJ1bmlmKG4gPSBMZW4pCiAgICBzb3J0X2xvY19ydW5pZiA8LSBzb3J0KGxvY19ydW5pZikKICAgIGRhdGEuZnJhbWUoY2FzZSA9IDE6TGVuLCByZWxlbWVudCA9IHNvcnRfbG9jX3J1bmlmKQp9CgpjYWxjX2JvdW5kIDwtIGZ1bmN0aW9uKGRmLCBib3VuZCl7CiAgICBxdWFudGlsZShkZiRyZWxlbWVudCwgYm91bmQpCn0KCm1ha2VfcXFkYXRhIDwtIGZ1bmN0aW9uKHB2ZWMsIG5ib290ID0gMTAwMDApewogICAgbG9jUCA8LSBwdmVjCgpMUCA8LSBsZW5ndGgobG9jUCkKIzE6MTAgJT4lIG1hcChydW5pZiwgbiA9IExQKQpzb3J0ZWRQIDwtIHNvcnQobG9jUCkKZXhwMiA8LSAxOmxlbmd0aChsb2NQKS9sZW5ndGgobG9jUCkKc29ydGVkRXhwUCA8LSBzb3J0KGV4cDIpCgpyYW5kb21fcHZhbHVlcyA8LSBkYXRhLmZyYW1lKGl0ZXIgPSAxOm5ib290KSAlPiUKbXV0YXRlKHJhbmQgPSBtYXAoaXRlciwgfm15X3J1bmlmKExlbiA9IExQKSkpICU+JSB1bm5lc3QgJT4lCm5lc3QoLWNhc2UpICU+JQptdXRhdGUobGIgPSBtYXBfZGJsKGRhdGEsIH5jYWxjX2JvdW5kKC4sIDAuMDI1KSksCiAgICAgIHViID0gbWFwX2RibChkYXRhLCB+Y2FsY19ib3VuZCguLDAuOTc1KSkpICU+JSBkcGx5cjo6c2VsZWN0ICgtZGF0YSkKCnFxZGF0YSA8LSBiaW5kX2NvbHMoc29ydGVkUCA9IHNvcnRlZFAsIHNvcnRlZEV4cFAgPSBzb3J0ZWRFeHBQLCByYW5kb21fcHZhbHVlcykKCnJldHVybihxcWRhdGEpCn0KYGBgCgpgYGB7cn0KcXFkYXRhX3Blcm1LZXJuTWlyIDwtIG1ha2VfcXFkYXRhKHBlcm1LZXJuVGFibGUkbWlyLlApCmBgYAoKYGBge3J9CnFxZGF0YV9wZXJtS2Vybk1pciAlPiUgc3RyCmBgYAoKYGBge3J9Cm9wdGlvbnMocmVwci5wbG90LndpZHRoPTYsIHJlcHIucGxvdC5oZWlnaHQ9IDYpCnFxZGF0YV9wZXJtS2Vybk1pciAlPiUgZ2dwbG90KGFlcyh4ID0gc29ydGVkRXhwUCwgeSA9IHNvcnRlZFApKSArIGdlb21fcG9pbnQoc2hhcGUgPSAxKSArIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSkgKyAKbGFicyh4ID0gZXhwcmVzc2lvbihwYXN0ZSgiRXhwZWN0ZWQgIiwgaXRhbGljKCJwIiksIi12YWx1ZSIpKSwgeSA9IGV4cHJlc3Npb24ocGFzdGUoIk9ic2VydmVkICIsIGl0YWxpYygicCIpLCItdmFsdWUiKSkpCmdnc2F2ZSgnZmlndXJlcy9xcV9wZXJtS2Vybk1pci5wbmcnKQpgYGAKCmBgYHtyfQpxcWRhdGFfcGVybUtlcm5NaXIgJT4lIGdncGxvdChhZXMoeCA9IHNvcnRlZEV4cFAsIHkgPSBzb3J0ZWRQKSkgKyBnZW9tX3BvaW50KHNoYXBlID0gMSkgKyBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEpICsgZ2VvbV9saW5lKGFlcyh5ID0gbGIpLCBjb2xvdXIgPSAiZ3JleTQwIikgKyBnZW9tX2xpbmUoYWVzKHkgPSB1YiksIGNvbG91ciA9ICJncmV5NDAiKSArIHNjYWxlX3lfbG9nMTAoKSArIHNjYWxlX3hfbG9nMTAoKQpgYGAKCldlIHNlZSBmcm9tIHRoZSBxcXBsb3QgdGhhdCB3ZSBnZW5lcmFsbHkgZmFsbCBiZWxvdyB0aGUgMToxIGxpbmUsIHN1Z2dlc3RpbmcgdGhhdCBvdXIgUCB2YWx1ZXMgYXJlIGxvd2VyIHRoYW4gd2Ugd291bGQgZXhwZWN0IGZyb20gY2hhbmNlLgpUbyBtYWtlIHRoZSBncmV5IGxpbmVzLCBJIHNhbXBsZWQgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIG9mIHJhbmRvbSBkcmF3cyBmcm9tIHRoZSB1bmlmb3JtIGRpc3RyaWJ1dGlvbiAoZXNzZW50aWFsbHkgcmFuZG9tIHAgdmFsdWVzKS4gVGhlIGRhdGEgcG9pbnRzIHNvbWV0aW1lcywgYnV0IG5vdCBhbHdheXMgZmFsbCBiZWxvdyB0aGUgbG93ZXIgb25lIG9mIHRoZXNlLiBIb3dldmVyLCB3ZSBjYXJlIGFib3V0IGFsbCBvZiB0aGUgcCB2YWx1ZXMsIG5vdCB3aGV0ZXIgZWFhY2ggaW5kaXZpZHVhbCBpcyBtb3JlIHVudXN1YWwgdGhhbiB3ZSB3b3VsZCBleHBlY3QgYnkgY2hhbmNlLiBJJ2xsIHJlcGxvcnQganVzdCB0aGUgcmVndWxhciBxcXBsb3QgYXMgYSBzdXBwbGVtZW50YWwgZmlndXJlLgoKIyMgQXMgYWJvdmUsIGJ1dCB0aGlzIHRpbWUgd2l0aCBnYXVzc2lhbiAtIGNvbnRpbnVvdXMgZGVwZW5kZW50IHZhcmlhYmxlcwoKYGBge3J9CnB0bSA9IHByb2MudGltZSgpCnRwcyA8LSBDYXBWYXIodGVzdC5pbW11bmUxLCBucGVybSA9IDk5OTksIHRyYW5zZm9ybWF0aW9uID0gZnVuY3Rpb24oeCl7amFjX2JveF9jb3hfMih4KX0sIGZhbWlseSA9ICdnYXVzc2lhbicpCnByb2MudGltZSgpIC0gcHRtCnRwcwpgYGAKCmBgYHtyfQojIFJ1biBhYm92ZSBmdW5jdGlvbiBhZ2FpbnN0IGV2ZXJ5IHJlbGV2YW50IHZhcmlhYmxlLgpwdG0gPC0gcHJvYy50aW1lKCkKCnVzZS5pbW11bmUgJT4lCmZpbHRlcihjdCA9PSAnVCcpICU+JQpncm91cF9ieSh0eXBlLCBhbnRpZ2VuLCBtb250aCkgJT4lCmRvKGRhdGEuZnJhbWUoQ2FwVmFyKC4sIG5wZXJtID0gam5wZXJtLAogICAgICAgICAgICAgICAgICAgICB0cmFuc2Zvcm1hdGlvbiA9IGZ1bmN0aW9uKHgpe2phY19ib3hfY294XzIoeCl9LAogICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSAnZ2F1c3NpYW4nKSkpIC0+IHBlcm1LZXJuVGFibGVHYXVzCnBlcm1LZXJuVGFibGVHYXVzCgpwcm9jLnRpbWUoKSAtIHB0bQpgYGAKCmBgYHtyfQojIENsZWFuIHVwIHNvIHdlIGp1c3Qgc2VlIHRoZSByZXN1bHRzIG9mIHRoZSBrZXJuZWwgcmVncmVzc2lvbiAKY29uY2lzZVBlcm1LZXJuVGFibGVHYXVzIDwtIHBlcm1LZXJuVGFibGVHYXVzICU+JSB1bmdyb3VwICU+JQptdXRhdGUoS2VybmVsX1EgPSBwMnEobWlyLlApLCBNRFMxX1EgPSBwMnEod3VmMS5QKSkgJT4lCmRwbHlyOjpzZWxlY3QoVHlwZSA9IHR5cGUsIEFudGlnZW4gPSBhbnRpZ2VuLCBNb250aCA9IG1vbnRoLCBLZXJuZWxfUCA9IG1pci5QLCBLZXJuZWxfUSwKICAgICAgICAgICAgICBNRFMxX1AgPSB3dWYxLlAsIE1EUzFfUSwgTURTMV9SMiA9IHd1ZjEuTWNGYWRkZW4sIE1EUzFfQ29lZiA9IHd1ZjEuY29lZikgJT4lCmFzLmRhdGEuZnJhbWUgJT4lIApwYXNzIAoKY29uY2lzZVBlcm1LZXJuVGFibGVHYXVzCgp3cml0ZS5jc3YoZm9ybWF0KGNvbmNpc2VQZXJtS2VyblRhYmxlR2F1cywgZGlnaXRzID0gMyksICd0YWJsZXMvY29uY2lzZVBlcm1rZXJuVGFibGVHYXVzLmNzdicpCmBgYAoKIyMjIFRhYmxlIFMxCgpgYGB7cn0KIyBleHBvcnQgY29uZGl0aW9uYWxseSBmb3JtYXR0ZWQgdGFibGUgYXMgaHRtbApjb25jaXNlUGVybUtlcm5UYWJsZUdhdXMgJT4lCm11dGF0ZSgKICAgICMgdGhpcyByb3cgbmVlZHMgdG8gaGFwcGVuIGZpcnN0LCBzaW5jZSB0aGUgcmVmb3JtYXR0aW5nIG9mIHRoZSBub3RoZXIgbnVtYmVycyBtYWtlcyB0aGVtIGhhcmRlciB0byBjYWxsCiAgICAgICBNRFMxX0NvZWYgPSBjZWxsX3NwZWMoZm9ybWF0KE1EUzFfQ29lZiwgZGlnaXRzID0gMyksICJodG1sIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9sZCA9IGlmZWxzZShLZXJuZWxfUCA8IDAuMDUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgaXRhbGljID0gaWZlbHNlKEtlcm5lbF9QIDwgMC4wNSAmIE1EUzFfQ29lZiA8IDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEYpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBpZmVsc2UoS2VybmVsX1AgPCAwLjA1LCBpZmVsc2UoTURTMV9Db2VmIDwgMCwgImxpZ2h0c2FsbW9uIiwgImxpZ2h0Ymx1ZSIpLCAiIikKICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICBLZXJuZWxfUCA9IGNlbGxfc3BlYyhmb3JtYXRfcm91bmQoS2VybmVsX1AsIDMpLCAiaHRtbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKEtlcm5lbF9QIDwgMC4wNSwgVCwgRiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gaWZlbHNlKEtlcm5lbF9QIDwgMC4wNSwgJ3llbGxvdycsICcnKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgS2VybmVsX1EgPSBjZWxsX3NwZWMoZm9ybWF0X3JvdW5kKEtlcm5lbF9RLCAzKSwgImh0bWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9sZCA9IGlmZWxzZShLZXJuZWxfUSA8IDAuMiwgVCwgRiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gaWZlbHNlKEtlcm5lbF9RIDwgMC4yLCAnbGlnaHR5ZWxsb3cnLCAnJykKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICBNRFMxX1AgID0gY2VsbF9zcGVjKGZvcm1hdF9yb3VuZChNRFMxX1AsIDMpLCAiaHRtbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKE1EUzFfUCA8IDAuMDUsIFQsIEYpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IGlmZWxzZShNRFMxX1AgPCAwLjA1LCAneWVsbG93JywgJycpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICBNRFMxX1EgPSBjZWxsX3NwZWMoZm9ybWF0X3JvdW5kKE1EUzFfUSwgMyksICJodG1sIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvbGQgPSBpZmVsc2UoTURTMV9RIDwgMC4yLCBULCBGKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBpZmVsc2UoTURTMV9RIDwgMC4yLCAnbGlnaHR5ZWxsb3cnLCAnJykKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgIE1vbnRoID0gY2VsbF9zcGVjKE1vbnRoLCAiaHRtbCIpCgogICAgCiAgICAgICkgJT4lCgptdXRhdGUoQW50aWdlbiA9IGdzdWIoJ0FOWS5FTlYuUFRFRycsICdBbnkgRU5WIFBURUcnLCBBbnRpZ2VuKSkgJT4lCm11dGF0ZShBbnRpZ2VuID0gZ3N1YignZ3A3MF9CLkNhc2VBX1YxX1YyJywgJ2dwNzAgQi5DYXNlQSBWMS1WMicsIEFudGlnZW4pKSAtPiB0b1RhYmxlCgp0b1RhYmxlICU+JQoKa2FibGUoImh0bWwiLCBlc2NhcGUgPSBGLCBkaWdpdHMgPSAzLCBhbGlnbiA9ICdjJywgY29sLm5hbWVzID0gY29sTmFtZXMyKSAlPiUKa2FibGVfc3R5bGluZygic3RyaXBlZCIsICJob3ZlciIsIGZ1bGxfd2lkdGggPSBGKSAlPiUKYWRkX2hlYWRlcl9hYm92ZShjb2xOYW1lczEpICU+JQpjb2xsYXBzZV9yb3dzKGNvbHVtbnMgPSAxOjIsIGxhdGV4X2hsaW5lID0gImZ1bGwiKSAtPiBjb25jaXNlUGVybUtlcm5UYWJsZUdhdXMuaHRtbAoKY29uY2lzZVBlcm1LZXJuVGFibGVHYXVzLmh0bWwKCmNvbmNpc2VQZXJtS2VyblRhYmxlR2F1cy5odG1sICU+JSBjYXQoZmlsZSA9ICd0YWJsZXMvY29uY2lzZVBlcm1rZXJuVGFibGVHYXVzLmh0bWwnKQpgYGAKCmBgYHtyfQpjb25jaXNlUGVybUtlcm5UYWJsZUdhdXMgJT4lIGZpbHRlcihLZXJuZWxfUCA8IDAuMDUpIC0+IHNob3J0UGVybWtlcm5UYWJsZUdhdXMKc2hvcnRQZXJta2VyblRhYmxlR2F1cwp3cml0ZS5jc3YoZm9ybWF0KHNob3J0UGVybWtlcm5UYWJsZUdhdXMsIGRpZ2l0cyA9IDMpLCAndGFibGVzL3Nob3J0UGVybWtlcm5UYWJsZS5jc3YnKQpgYGAKCmBgYHtyfQojIE1ha2UgbGF0ZXggdGFibGUKCmNvbmNpc2VQZXJtS2VyblRhYmxlR2F1cyAlPiUKbXV0YXRlKAogICAgIyB0aGlzIHJvdyBuZWVkcyB0byBoYXBwZW4gZmlyc3QsIHNpbmNlIHRoZSByZWZvcm1hdHRpbmcgb2YgdGhlIG5vdGhlciBudW1iZXJzIG1ha2VzIHRoZW0gaGFyZGVyIHRvIGNhbGwKICAgIE1EUzFfQ29lZiA9IGNlbGxfc3BlYyhmb3JtYXQoTURTMV9Db2VmLCBkaWdpdHMgPSAzKSwgImh0bWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKEtlcm5lbF9QIDwgMC4wNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBpdGFsaWMgPSBpZmVsc2UoS2VybmVsX1AgPCAwLjA1ICYgTURTMV9Db2VmIDwgMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IGlmZWxzZShLZXJuZWxfUCA8IDAuMDUsIGlmZWxzZShNRFMxX0NvZWYgPCAwLCAibGlnaHRzYWxtb24iLCAibGlnaHRibHVlIiksICIiKQogICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgIEtlcm5lbF9QID0gY2VsbF9zcGVjKGZvcm1hdF9yb3VuZChLZXJuZWxfUCwgMyksICJsYXRleCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKEtlcm5lbF9QIDwgMC4wNSwgVCwgRiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gaWZlbHNlKEtlcm5lbF9QIDwgMC4wNSwgJ3llbGxvdycsICd3aGl0ZScpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICBLZXJuZWxfUSA9IGNlbGxfc3BlYyhmb3JtYXRfcm91bmQoS2VybmVsX1EsIDMpLCAibGF0ZXgiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9sZCA9IGlmZWxzZShLZXJuZWxfUSA8IDAuMiwgVCwgRiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gaWZlbHNlKEtlcm5lbF9RIDwgMC4yLCAnZ3JlZW4nLCAnd2hpdGUnKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgIE1EUzFfUCAgPSBjZWxsX3NwZWMoZm9ybWF0X3JvdW5kKE1EUzFfUCwgMyksICJsYXRleCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKE1EUzFfUCA8IDAuMDUsIFQsIEYpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IGlmZWxzZShNRFMxX1AgPCAwLjA1LCAneWVsbG93JywgJ3doaXRlJykKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgIE1EUzFfUSA9IGNlbGxfc3BlYyhmb3JtYXRfcm91bmQoTURTMV9RLCAzKSwgImxhdGV4IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvbGQgPSBpZmVsc2UoTURTMV9RIDwgMC4yLCBULCBGKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBpZmVsc2UoTURTMV9RIDwgMC4yLCAnZ3JlZW4nLCAnd2hpdGUnKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgI01vbnRoID0gY2VsbF9zcGVjKGZvcm1hdF9yb3VuZChNb250aCwwKSwgImh0bWwiKQogICAgTW9udGggPSBjZWxsX3NwZWMoTW9udGgsICJsYXRleCIpCgogICAgCiAgICAgICkgJT4lCm11dGF0ZShBbnRpZ2VuID0gZ3N1YignQU5ZLkVOVi5QVEVHJywgJ0FueSBFTlYgUFRFRycsIEFudGlnZW4pKSAlPiUKbXV0YXRlKEFudGlnZW4gPSBnc3ViKCdncDcwX0IuQ2FzZUFfVjFfVjInLCAnZ3A3MCBCLkNhc2VBIFYxLVYyJywgQW50aWdlbikpIC0+IHRvVGFibGUKCnRvVGFibGUgJT4lIAprYWJsZSgibGF0ZXgiLCBlc2NhcGUgPSBGLCBkaWdpdHMgPSAzLCBhbGlnbiA9ICdjJywgY29sLm5hbWVzID0gY29sTmFtZXMyLCBib29rdGFicyA9IFQpICU+JQprYWJsZV9zdHlsaW5nKHBvc2l0aW9uID0gImxlZnQiKSAlPiUKCmFkZF9oZWFkZXJfYWJvdmUoY29sTmFtZXMxKSAlPiUKY29sbGFwc2Vfcm93cyhjb2x1bW5zID0gMToyLCBsYXRleF9obGluZSA9ICJmdWxsIikgJT4lCnBhc3MgLT4gY29uY2lzZVBlcm1LZXJuVGFibGVHYXVzLnRleApgYGAKCmBgYHtyfQojIFByaW50IGxhdGV4IHRhYmxlIHRvIHRleCBmaWxlCgpjYXQoZG9jSGVhZCwgY29uY2lzZVBlcm1LZXJuVGFibGVHYXVzLnRleCwgZG9jVGFpbCwgZmlsZSA9ICd0YWJsZXMvY29uY2lzZVBlcm1rZXJuVGFibGVHYXVzLnRleCcpCmBgYAoKYGBge3J9CiMjIyBRUXBsb3QgZm9yIGtlcm5lbCByZWdyZXNzaW9uIGRhdGEKYGBgCgpgYGB7cn0KcXFkYXRhX3Blcm1LZXJuTWlyR2F1cyA8LSBtYWtlX3FxZGF0YShwZXJtS2VyblRhYmxlR2F1cyRtaXIuUCkKYGBgCgpgYGB7cn0Kb3B0aW9ucyhyZXByLnBsb3Qud2lkdGg9NiwgcmVwci5wbG90LmhlaWdodD0gNikKcXFkYXRhX3Blcm1LZXJuTWlyR2F1cyAlPiUgZ2dwbG90KGFlcyh4ID0gc29ydGVkRXhwUCwgeSA9IHNvcnRlZFApKSArIGdlb21fcG9pbnQoc2hhcGUgPSAxKSArIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSkgKyAKbGFicyh4ID0gZXhwcmVzc2lvbihwYXN0ZSgiRXhwZWN0ZWQgIiwgaXRhbGljKCJwIiksIi12YWx1ZSIpKSwgeSA9IGV4cHJlc3Npb24ocGFzdGUoIk9ic2VydmVkICIsIGl0YWxpYygicCIpLCItdmFsdWUiKSkpCmdnc2F2ZSgnZmlndXJlcy9xcV9wZXJtS2Vybk1pckdhdXMucG5nJykKYGBgCgojIyBDaGkgU3F1YXJlZCB0ZXN0IGZvciBzdGF0aXN0aWNhbCBhc3NvY2lhdGlvbnMgYmV0d2VlbiBlYWNoIHBhaXIgb2YgaW1tdW5lIHZhcmlhYmxlcwoKYGBge3J9CnVzZS5pbW11bmUgJT4lIGRwbHlyOjpzZWxlY3QocHViX2lkLCB2aXNpdG5vLCB0eXBlLCBhbnRpZ2VuLCBtYWcpIC0+IHRtcApmdWxsX2pvaW4odG1wLCB0bXAsIGJ5ID0gJ3B1Yl9pZCcpICU+JSAKZ3JvdXBfYnkodmlzaXRuby54LCB0eXBlLngsIGFudGlnZW4ueCwgdmlzaXRuby55LCB0eXBlLnksIGFudGlnZW4ueSkgJT4lCm5lc3QgJT4lCm11dGF0ZSh4MiA9IG1hcChkYXRhLCBmdW5jdGlvbihkZil7dW53YXJuKGNoaXNxLnRlc3QoZGYkbWFnLngsIGRmJG1hZy55KSl9KSkgJT4lCm11dGF0ZShnbGFuY2UgPSBtYXAoeDIsIGdsYW5jZSkpICU+JQpkcGx5cjo6c2VsZWN0KC1kYXRhLCAteDIpICU+JQp1bm5lc3QoZ2xhbmNlKSAlPiUKI211dGF0ZShxLnZhbHVlID0gcDJxKHAudmFsdWUpKSAlPiUgIyByZXVybnMgTmFOcwpwYXNzIC0+IGNvbXBhcmVJbW11bmVYMgpgYGAKCmBgYHtyfQpjb21wYXJlSW1tdW5lWDIgJT4lIGZpbHRlcigKICAgIHR5cGUueCA9PSAnSWdHJyAmCiAgICBhbnRpZ2VuLnggPT0gJ2dwNDEnICYKICAgIHR5cGUueSA9PSAnSWdHJyAmCiAgICBhbnRpZ2VuLnkgPT0gJ2dwNDEnCikKYGBgCgpgYGB7cn0KY29tcGFyZUltbXVuZVgyICU+JQpmaWx0ZXIodHlwZS54ID09ICdJZ0cnICYgdHlwZS55ID09ICdJZ0cnICYgYW50aWdlbi54ICE9IGFudGlnZW4ueSkgJT4lCndyaXRlX2NzdigndGFibGVzL2NoaXNxX0lnR19jb21wYXJhc29ucy5jc3YnKQpgYGAKCiMgTURTIEdMTSBmb3IgZWFjaCBvdGhlciBNRFMgQXhpcwoKYGBge3J9CnBzTjIgJT4lIHBoeWxvX2pvaW4oc2NvcmVzKHBzTjIucGNvYSwgZGlzcGxheSA9ICJzaXRlcyIsIGNob2ljZXMgPSAxOjEwKSAlPiUKICAgICAgICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lICU+JSByb3duYW1lc190b19jb2x1bW4sIGJ5ID0gJ3Jvd25hbWUnKSAlPiUKc2FtcGxlX2RhdGEgJT4lIGFzKCdkYXRhLmZyYW1lJykgJT4lIHJvd25hbWVzX3RvX2NvbHVtbiAtPiBoZXJlU2FtCmBgYAoKYGBge3J9CmRhdGFfZnJhbWUoZm9ybXVsYSA9IHBhc3RlKCJ0cmFuc2Zvcm1hdGlvbih5ZGF0YSkgfiBNRFMiLCAxOjEwLCBzZXAgPSAiIikpCmBgYAoKYGBge3J9CkVhY2hNRFMgPC0gZnVuY3Rpb24oeCwgbnBlcm0gPSA5OTk5LCB0cmFuc2Zvcm1hdGlvbiA9IG1lZGNvZGUyLCBmYW1pbHkgPSAnYmlub21pYWwnKXsKICAgICMjIFB1bGwgb3V0IHRoZSBuZWVkZWQgZGF0YQogICAgCiAgICBwc04yLndNRFMgPC0gcHNOMiAlPiUgcGh5bG9fam9pbihzY29yZXMocHNOMi5wY29hLCBkaXNwbGF5ID0gInNpdGVzIiwgY2hvaWNlcyA9IDE6MTApICU+JQogICAgICAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUgJT4lIHJvd25hbWVzX3RvX2NvbHVtbiwgYnkgPSAncm93bmFtZScpCiAgICAKIyAgICAgbWVkV3VmIDwtIE5BCiMgICAgIHJhbmtXdWYgPC0gTkEKICAgIGxvY1BTIDwtIHBoeWxvX2pvaW4ocHNOMi53TURTLCB4LCBieSA9ICdwdWJfaWQnKSAKICAgIHlkYXRhMCA8LSBzYW1wbGVfZGF0YShsb2NQUykkbWFnCiAgICB5bmEgPC0gaXMubmEoeWRhdGEwKQogICAgI2xvYy53dWYgPC0gd3VmS04yCiAgICAjbG9jLmpzZCA8LSBqc2RLTjIKICAgIHlkYXRhIDwtIHlkYXRhMAogICAgCiAgICB5ZGF0YSA8LSB5ZGF0YTBbIXluYV0KICAgIGxvYy53dWYyIDwtIHBzTjIud3VmICU+JSBhcy5tYXRyaXggJT4lIC5bIXluYSwgIXluYV0KICAgIAogICAgIHNhbURmIDwtIGxvY1BTICU+JSBzYW1wbGVfZGF0YSAlPiUgYXMoJ2RhdGEuZnJhbWUnKSAlPiUgcm93bmFtZXNfdG9fY29sdW1uICU+JQogICAgLlsheW5hLF0KCiMgICAgICMgSXMgZ2l2aW5nIG9ubHkgcG9zaXRpdmUgcmVzdWx0cyB3aXRoIENBUDEsIG5vdCBzdXJlIHdoeQogICAgbG9jX2dsbSA8LSBnbG0oYXMuZm9ybXVsYSgidHJhbnNmb3JtYXRpb24oeWRhdGEpIH4gIE1EUzEiKSwgZGF0YSA9IHNhbURmLCBmYW1pbHkgPSBmYW1pbHkpCiAgICBnbG1Bbm92YSA8LSBsb2NfZ2xtICU+JSBhbm92YSh0ZXN0ID0gIkNoaXNxIikKICAgIAogICAgIyBkYXRhX2ZyYW1lLCByYXRoZXIgdGhhbiBkYXRhLmZyYW1lCiAgICAjIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQ4NDUwMzA4L2l0ZXJhdGluZy1vdmVyLWZvcm11bGFzLWluLXB1cnJyIzQ4NDUwMzA4CiAgICBkYXRhX2ZyYW1lKGZvcm11bGFTdHJpbmcgPSBwYXN0ZSgidHJhbnNmb3JtYXRpb24oeWRhdGEpIH4gTURTIiwgMToxMCwgc2VwID0gIiIpKSAlPiUKICAgICBtdXRhdGUobW9kZWwgPSBtYXAoZm9ybXVsYVN0cmluZywgZnVuY3Rpb24oZnMpewogICAgICAgICBnbG0oYXMuZm9ybXVsYShmcyksIGRhdGEgPSBzYW1EZiwgZmFtaWx5ID0gZmFtaWx5KX0pKSAlPiUKICAgIG11dGF0ZShhbm92YSA9IG1hcChtb2RlbCwgYW5vdmEpKSAlPiUKICAgIG11dGF0ZShnbGFuY2UgPSBtYXAobW9kZWwsIGdsYW5jZSkpICU+JQogICAgbXV0YXRlKHRpZHkgPSBtYXAobW9kZWwsIHRpZHkpKSAlPiUKICAgIG11dGF0ZShjb2VmID0gbWFwKG1vZGVsLCB+IGNvZWYoc3VtbWFyeSguKSlbMixdKSkgJT4lCiAgICBwYXNzIC0+IGFsbG1vZGVscwoKICAgIGFsbG1vZGVscyAlPiUgZHBseXI6OnNlbGVjdCgidGlkeSIpICU+JSB1bm5lc3QgJT4lIGZpbHRlcih0ZXJtICE9ICcoSW50ZXJjZXB0KScpCiAgICAKIAogICAgfQogICAgCmBgYAoKYGBge3J9CiMgSnVzdCBjb25maXJtaW5nIHRoYXQgdGhlIGZ1bmN0aW9uIHdvcmtzIGJlZm9yZSBpdCBnb2VzIGluIGEgZ2lhbnQgbG9vcC4gSSdkIGRlbGV0ZSB0aGlzLAojIGJ1dCBpJ2xsIGp1c3QgZW5kIHVwIG5lZWRpbmcgaXQgYWdhaW4gaWYgSSBkby4KcHRtID0gcHJvYy50aW1lKCkKdHBzIDwtIEVhY2hNRFModGVzdC5pbW11bmUucHRlZywgbnBlcm0gPSA5OTk5LCB0cmFuc2Zvcm1hdGlvbiA9IG1lZGNvZGUsIGZhbWlseSA9ICdiaW5vbWlhbCcpCnByb2MudGltZSgpIC0gcHRtCmBgYAoKYGBge3J9CnRwcwpgYGAKCmBgYHtyfQp1c2UuaW1tdW5lICU+JQpncm91cF9ieSh0eXBlLCBhbnRpZ2VuLCBtb250aCkgJT4lCm5lc3QgJT4lCm11dGF0ZShjb2VmcyA9IG1hcChkYXRhLCB+IEVhY2hNRFMoLikpKSAlPiUKZHBseXI6OnNlbGVjdCgtZGF0YSkgJT4lIHVubmVzdChjb2VmcykgLT4gZ2xtTURTY29lZnMKYGBgCgpgYGB7cn0KYW50czEKYGBgCgpgYGB7cn0KZ2xtTURTY29lZnMgJT4lCmdhdGhlcihrZXkgPSAia2V5IiwgdmFsdWUgPSAidmFsdWUiLCBlc3RpbWF0ZTpwLnZhbHVlKSAlPiUKZmlsdGVyKGtleSA9PSAicC52YWx1ZSIpICU+JQpzcHJlYWQoa2V5ID0gdGVybSwgdmFsdWUgPSB2YWx1ZSkgJT4lCmRwbHlyOjpzZWxlY3QoLWtleSwgLU1EUzEwLCBNRFMxMCkgJT4lCmRwbHlyOjpyZW5hbWUoVHlwZSA9IHR5cGUsIEFudGlnZW4gPSBhbnRpZ2VuLCBNb250aCA9IG1vbnRoKSAlPiUKbXV0YXRlKFR5cGUgPSBmYWN0b3IoVHlwZSwgbGV2ZWxzID0gYyggIklnQSIsICJJZ0ciLCAgIkNENCsiKSkpICU+JQptdXRhdGUoQW50aWdlbiA9IGZhY3RvcihBbnRpZ2VuLCBsZXZlbHMgPSBjKGFudHMxLCBhbnRzMiwgIkFOWS5FTlYuUFRFRyIpKSkgJT4lCiNDbGVhbiB1cCBsYWJlbHMKbXV0YXRlKEFudGlnZW4gPSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwoQW50aWdlbiwgIl8iLCAiICIpKSAlPiUKbXV0YXRlKEFudGlnZW4gPSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwoQW50aWdlbiwgIlYxIFYyIiwgIlYxLVYyIikpICU+JQptdXRhdGUoQW50aWdlbiA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChBbnRpZ2VuLCAiQU5ZLkVOVi5QVEVHIiwgIkFueSBFTlYgUFRFRyIpKSAlPiUKYXJyYW5nZShUeXBlKSAtPiBhbGxNRFMKYWxsTURTCmBgYAoKIyMjIFRhYmxlIFMyCgpgYGB7cn0KYWxsTURTICU+JQprYWJsZSgiaHRtbCIsIGVzY2FwZSA9IEYsIGRpZ2l0cyA9IDMsIGFsaWduID0gJ2MnKSAlPiUKY29sbGFwc2Vfcm93cyhjb2x1bW5zID0gMToyLCBsYXRleF9obGluZSA9ICJmdWxsIikgJT4lCmFzLmNoYXJhY3RlcigpIC0+IGFsbE1EUy5odG1sCgphbGxNRFMgJT4lCmthYmxlKCJodG1sIiwgZXNjYXBlID0gRiwgZGlnaXRzID0gMywgYWxpZ24gPSAnYycpICU+JQpjb2xsYXBzZV9yb3dzKGNvbHVtbnMgPSAxOjIsIGxhdGV4X2hsaW5lID0gImZ1bGwiKQoKCmFsbE1EUy5odG1sCgphbGxNRFMuaHRtbCAlPiUgY2F0KGZpbGUgPSAndGFibGVzL2FsbE1EUy5odG1sJykKYGBgCgpgYGB7cn0KYWxsTURTICU+JQprYWJsZSgibGF0ZXgiLCBlc2NhcGUgPSBGLCBkaWdpdHMgPSAzLCBhbGlnbiA9ICdjJywgYm9va3RhYnMgPSBUKSAlPiUKY29sbGFwc2Vfcm93cyhjb2x1bW5zID0gMToyLCBsYXRleF9obGluZSA9ICJmdWxsIikgJT4lCnBhc3MgLT4gYWxsTURTLmxhdGV4CgoKYWxsTURTLmxhdGV4ICU+JSBjYXQoZmlsZSA9ICd0YWJsZXMvYWxsTURTLnRleCcpCmBgYAoKYGBge3J9CiMgUHJpbnQgbGF0ZXggdGFibGUgdG8gdGV4IGZpbGUKY2F0KGRvY0hlYWQsIGFsbE1EUy5sYXRleCwgZG9jVGFpbCwgZmlsZSA9ICd0YWJsZXMvYWxsTURTLnRleCcpCmBgYAoKYGBge3J9CndyaXRlX2NzdihhbGxNRFMsICd0YWJsZXMvYWxsTURTR2xtUFZhbHVlcy5jc3YnKQpgYGAKCiMgSmVuc2VuIFNoYW5ub24gS2VybmVsIFJlZ3Jlc3Npb24KYXQgZWFjaCB0YXhvbm9taWMgbGV2ZWwKCiMjIEFnZ2xvbWVyYXRpb24KCmBgYHtyfQojIEhvdyBtYW55IHRheGEgZG8gd2Ugc2VlIGlmIHdlIGFnZ2xvbWVyYXRlIGF0IGRpZmZlcmVudCBsZXZlbHMKcHNOMiAlPiUgdGF4X3RhYmxlICU+JSBhcy5kYXRhLmZyYW1lICU+JSBkcGx5cjo6c2VsZWN0KFBoeWx1bTpHZW51cykgJT4lIGNvbG5hbWVzIC0+IHRheExldmVscwoKZGF0YV9mcmFtZSh0YXhMZXZlbHMpICU+JQptdXRhdGUobnRheGEgPSBtYXAodGF4TGV2ZWxzLAogICAgZnVuY3Rpb24obGV2KXsKICAgICAgICBwc04yICU+JSB0YXhfZ2xvbShsZXYpICU+JSBudGF4YQogICAgfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSAlPiUKbXV0YXRlKG50YXhhID0gdW5saXN0KG50YXhhKSkgJT4lCnBhc3MgLT4gTlRheGFBdExldmVsCk5UYXhhQXRMZXZlbApgYGAKCmBgYHtyfQpkYXRhX2ZyYW1lKHRheExldmVscyA9ICJTcGVjaWVzIiwgbnRheGEgPSBudGF4YShwc04yKSwgcHMgPSBsaXN0KChwc04yKSkpIC0+IHNwZWNSb3cKZGF0YV9mcmFtZSh0YXhMZXZlbHMgPSAiU3BlY2llcyIsIG50YXhhID0gbnRheGEocHNOMSksIHBzQ291bnQgPSBsaXN0KChwc04xKSkpIC0+IHNwZWNSb3dDCmBgYAoKYGBge3J9CkQyS19zYXZlbmFtZSA8LSBmdW5jdGlvbihkaXN0bWF0KXsKICAgICMgY2FzY2FkZSBuYW1lcyBmb3J3YXJkIHdpdGggdGhlIEQySyBvcGVyYXRpb24KICAgIHJlcXVpcmUoTWlSS0FUKQogICAgb3V0IDwtIE1pUktBVDo6RDJLKGRpc3RtYXQpCiAgICBjb2xuYW1lcyhvdXQpIDwtIGNvbG5hbWVzKGRpc3RtYXQpCiAgICByb3duYW1lcyhvdXQpIDwtIHJvd25hbWVzKGRpc3RtYXQpCiAgICBvdXQKfQpgYGAKCmBgYHtyfQojIERhdGEgZnJhbWUgb2YgcGh5bG9zZXEgb2JqZWN0cyBkaXN0YW5jZXMgYW5kIGtlcm5lbHMgYXQgYSBidW5jaCBvZiB0YXhvbm9taWMgbGV2ZWxzCk5UYXhhQXRMZXZlbCAlPiUKbXV0YXRlKHBzID0gbWFwKG50YXhhLCB+dGlwX2dsb21fc2F2ZWlkKHBzTjIsIGsgPSAuKSkpICU+JQojIHByb2Nlc3MgdGhlIHBoeWxvc2VxIG9iamVjdHMgc28gdGhleSBoYXZlIGJldHRlciBuYW1lcwptdXRhdGUocHMgPSBtYXAocHMsIH5zd2FwLnBoeWxvc2VxLnRheG5hbWVzKHRhZ19waHlsb3NlcShyZW1vdmVfdGFnX3BoeWxvc2VxKC4pKSwgb2xkbmFtZSA9ICdvbGRuYW1lMicpKSkgJT4lCiMgYWRkIGluIHRoZSBzcGVjaWVzIGRhdGEgcm93ICh3aGljaCBzaG91bGQgYWxyZWFkeSBoYXZlIGNvcnJlY3QgbmFtZXMpCmJpbmRfcm93cyhzcGVjUm93KSAlPiUKIyBjYWxjdWxhdGUgamVuc2VuLXNoYW5ub24gZGlzdGFuY2UgbWF0cml4Cm11dGF0ZShqc2QgPSBtYXAocHMsIH5waHlsb3NlcTo6ZGlzdGFuY2UoLiwgbWV0aG9kID0gImpzZCIpICkpICU+JQojIGNvbnZlcnQgdG8gMmQgbWF0cml4Cm11dGF0ZShqc2RNYXQgPSBtYXAoanNkLCB+YXMubWF0cml4KC4pKSkgJT4lCiMgY2FsY3VsYXRlIGtlcm5lbAptdXRhdGUoa2pzZCA9IG1hcChqc2RNYXQsIH5EMktfc2F2ZW5hbWUoLikpKSAtPiB0bXAKCnRtcCAlPiUKbXV0YXRlKHBzTm9aZXJvID0gbWFwKHBzLCB+dHJhbnNmb3JtX3NhbXBsZV9jb3VudHMoLiwgZnVuY3Rpb24oeCkgeCsoMS8xMDAwKSkpKSAtPiB0bXAKCnRtcCAlPiUKIyMgY2hlbW9tZXRyaWNzOjpjbHIganVzdCB3b3Jrcywgd2hpbGUgY29tcG9zaXRpb25zOjpjbHIgdGhyb3dzIGEgY3JpcHRpYyBlcnJvciBtZXNzYWdlIGhlcmUKbXV0YXRlKGNsciA9IG1hcChwc05vWmVybywgfiB0cmFuc2Zvcm1fb3R1X3RhYmxlKC4sIGNoZW1vbWV0cmljczo6Y2xyKSkpICU+JQojbXV0YXRlKGNsciA9IG1hcChwc05vWmVybywgfiB0cmFuc2Zvcm1fb3R1X3RhYmxlKC4sIGZ1bmN0aW9uKHgpIGFzLm1hdHJpeChjb21wb3NpdGlvbnM6OmNscih4KSkpKSkgJT4lCnBhc3MgLT4gcHNEZjAgIyBPcmlnaW5hbCB3YXkKYGBgCgpgYGB7cn0KIyBEYXRhIGZyYW1lIG9mIHBoeWxvc2VxIG9iamVjdHMgZGlzdGFuY2VzIGFuZCBrZXJuZWxzIGF0IGEgYnVuY2ggb2YgdGF4b25vbWljIGxldmVscwojIEkgdXNlIHBzTjEgYmVjYXVzZSBJIG5lZWQgY291bnQgZGF0YSBmb3Igc29tZSBkb3duc3RyZWFtIHN0ZXBzLgpOVGF4YUF0TGV2ZWwgJT4lCm11dGF0ZShwc0NvdW50ID0gbWFwKG50YXhhLCB+dGlwX2dsb21fc2F2ZWlkKHBzTjEsIGsgPSAuKSkpICU+JQojIHByb2Nlc3MgdGhlIHBoeWxvc2VxIG9iamVjdHMgc28gdGhleSBoYXZlIGJldHRlciBuYW1lcwptdXRhdGUocHNDb3VudCA9IG1hcChwc0NvdW50LCB+c3dhcC5waHlsb3NlcS50YXhuYW1lcyh0YWdfcGh5bG9zZXEocmVtb3ZlX3RhZ19waHlsb3NlcSguKSksIG9sZG5hbWUgPSAnb2xkbmFtZTInKSkpICU+JQojIGFkZCBpbiB0aGUgc3BlY2llcyBkYXRhIHJvdyAod2hpY2ggc2hvdWxkIGFscmVhZHkgaGF2ZSBjb3JyZWN0IG5hbWVzKQpiaW5kX3Jvd3Moc3BlY1Jvd0MpICU+JQpwYXNzIC0+IHRtcApgYGAKCmBgYHtyfQp0bXAgJT4lCiMgY2FsY3VsYXRlIGplbnNlbi1zaGFubm9uIGRpc3RhbmNlIG1hdHJpeAptdXRhdGUocHMgPSBtYXAocHNDb3VudCwgfnRyYW5zZm9ybV9zYW1wbGVfY291bnRzKC4sIGZ1bmN0aW9uKHgpIHt4L3N1bSh4KX0pKSkgJT4lCm11dGF0ZShqc2QgPSBtYXAocHMsIH5waHlsb3NlcTo6ZGlzdGFuY2UoLiwgbWV0aG9kID0gImpzZCIpICkpICU+JQojIGNvbnZlcnQgdG8gMmQgbWF0cml4Cm11dGF0ZShqc2RNYXQgPSBtYXAoanNkLCB+YXMubWF0cml4KC4pKSkgJT4lCiMgY2FsY3VsYXRlIGtlcm5lbAptdXRhdGUoa2pzZCA9IG1hcChqc2RNYXQsIH5EMktfc2F2ZW5hbWUoLikpKSAtPiB0bXAKCnRtcCAlPiUKbXV0YXRlKHBzTm9aZXJvID0gbWFwKHBzLCB+dHJhbnNmb3JtX3NhbXBsZV9jb3VudHMoLiwgZnVuY3Rpb24oeCkgeCsoMS8xMDAwKSkpKSAlPiUKIyMgY2hlbW9tZXRyaWNzOjpjbHIganVzdCB3b3Jrcywgd2hpbGUgY29tcG9zaXRpb25zOjpjbHIgdGhyb3dzIGEgY3JpcHRpYyBlcnJvciBtZXNzYWdlIGhlcmUKbXV0YXRlKGNsciA9IG1hcChwc05vWmVybywgfiB0cmFuc2Zvcm1fb3R1X3RhYmxlKC4sIGNoZW1vbWV0cmljczo6Y2xyKSkpICU+JQojbXV0YXRlKGNsciA9IG1hcChwc05vWmVybywgfiB0cmFuc2Zvcm1fb3R1X3RhYmxlKC4sIGZ1bmN0aW9uKHgpIGFzLm1hdHJpeChjb21wb3NpdGlvbnM6OmNscih4KSkpKSkgJT4lCnBhc3MgLT4gcHNEZgpgYGAKCmBgYHtyfQpwcmludChwc0RmKQpgYGAKCmBgYHtyfQpNaXJNdWx0aSA8LSBmdW5jdGlvbih4LCBLc0RmID0gcHNEZiwgcHMgPSBwc04yLCBucGVybSA9IDk5OTkpewogICAgCiAgICBLcyA9IEtzRGYka2pzZAogICAgCiAgICAjIEkgIGJpbmQgdG8gdGhlIHBoeWxvc2VxIG9iamVjdCBhbmQgdGhlbiBwZWVsIG9mZiBhZ2FpbiBsYXRlciB0byBndWVyZW50ZWUKICAgICMgdGhhdCB0aGUgeS1kYXRhIGlzIGluIHRoZSBzYW1lIG9yZGVyIGFzIHRoZSBLcwogICAgbG9jUFMgPC0gcGh5bG9fam9pbihwcywgeCwgYnkgPSAncHViX2lkJykKICAgIAogICAgeWRhdGEwIDwtIHNhbXBsZV9kYXRhKGxvY1BTKSRtYWcKICAgIHluYSA8LSBpcy5uYSh5ZGF0YTApCgogICAgeWRhdGEgPC0geWRhdGEwWyF5bmFdCiAgICBsb2MuS3MgPC0gbGFwcGx5KEtzLCBmdW5jdGlvbihLKXtLWyF5bmEsICF5bmFdfSkgIAogIAogICAgYmN4SlNEIDwtIE1pUktBVCh5ID0gamFjX2JveF9jb3hfMih5ZGF0YSksIEtzID0gbG9jLktzLCBvdXRfdHlwZSA9ICJDIiwgbWV0aG9kID0gJ3Blcm11dGF0aW9uJywgbnBlcm0gPSBucGVybSkKICAgIG1lZEpTRCA8LSBNaVJLQVQoeSA9IG1lZGNvZGUoeWRhdGEpLCBLcyA9IGxvYy5Lcywgb3V0X3R5cGUgPSAiRCIsIG1ldGhvZCA9ICdwZXJtdXRhdGlvbicsIG5wZXJtID0gbnBlcm0pCiAgICBtbURmID0gZGF0YS5mcmFtZSgKICAgICAgICB0YXhMZXZlbHMgPSBLc0RmJHRheExldmVscywKICAgICAgICBudGF4YSA9IEtzRGYkbnRheGEsCiAgICAgICAgYmN4SlNEID0gYmN4SlNEJGluZGl2UCwgbWVkSlNEID0gbWVkSlNEJGluZGl2UCwKICAgICAgICBiY3hKU0RPbW5pID0gYmN4SlNEJG9tbmlidXNfcCwgbWVkSlNET21uaSA9IG1lZEpTRCRvbW5pYnVzX3ApCiAgICBtbURmCiAgICAKICAgIH0KYGBgCgpgYGB7cn0KIyBUZXN0IGNhc2UKCnVzZS5pbW11bmUgJT4lCmZpbHRlcih0eXBlID09ICdJZ0cnICYgYW50aWdlbiA9PSAnZ3A0MScmIHZpc2l0bm8gPT0gMiAmIGN0ID09ICdUJykgLT4gdGVzdC5pbW11bmUxCgp0ZXN0Lm1tIDwtIE1pck11bHRpKHRlc3QuaW1tdW5lMSwgS3MgPSBwc0RmLCBucGVybSA9IDk5OSkKCnRlc3QubW0KYGBgCgpgYGB7cn0KcHRtID0gcHJvYy50aW1lKCkKCnVzZS5pbW11bmUgJT4lCmdyb3VwX2J5KHR5cGUsIGFudGlnZW4sIG1vbnRoKSAlPiUKbmVzdCAlPiUKbXV0YXRlKG1pciA9IG1hcChkYXRhLAogICAgfk1pck11bHRpKC4sIEtzID0gcHNEZiwgcHMgPSBwc04yLCBucGVybSA9IDk5OSkKKSkgJT4lCmRwbHlyOjpzZWxlY3QoLWRhdGEpICU+JSB1bm5lc3QobWlyKSAlPiUKcGFzcyAtPiBtaXJMZXZlbHMKCnByb2MudGltZSgpIC0gcHRtCmBgYAoKYGBge3J9Cm1pckxldmVscyAlPiUgZHBseXI6OnNlbGVjdCgtbnRheGEsIC1tZWRKU0QpICU+JSBzcHJlYWQoa2V5ID0gdGF4TGV2ZWxzLCB2YWx1ZSA9IGJjeEpTRCkKYGBgCgpgYGB7cn0KbWlyTGV2ZWxzICU+JSBkcGx5cjo6c2VsZWN0KC1udGF4YSwgLWJjeEpTRCkgJT4lIHNwcmVhZChrZXkgPSB0YXhMZXZlbHMsIHZhbHVlID0gbWVkSlNEKQpgYGAKCkknZCBsaWtlIHRvIGNvbWJpbmUgdGhlIGFib3ZlIGludG8gb25lIHRhYmxlLCB3aGVuIGl0IGlzbid0IDc6NDUuClByb2JhYmx5IGhhcyBzb2VtZXRoaW5nIHRvIGRvIHdpdGggbWVyZ2luZyBjb2x1bW5zIG9yIHNvbWV0aGluZy4KT3IgbWF5YmUgSSBqdXN0IHdhbnQgdG8gcGxvdCBpdCBhcyBhIGZpZ3VyZS4KCmBgYHtyfQptaXJMZXZlbHMgJT4lCmdhdGhlcihtZXRyaWMsIFAsIGJjeEpTRDptZWRKU0QpIC0+IG1pckRhdApgYGAKCmBgYHtyfQptaXJEYXQgJT4lIGRwbHlyOjpzZWxlY3QodHlwZTptb250aCwgYmN4SlNEID0gYmN4SlNET21uaSwgbWVkSlNEID0gbWVkSlNET21uaSkgJT4lCmdyb3VwX2J5KHR5cGUsIGFudGlnZW4sIG1vbnRoKSAlPiUKc3VtbWFyaXplKGJjeEpTRCA9IG1lYW4oYmN4SlNEKSwgbWVkSlNEID0gbWVhbihtZWRKU0QpKSAlPiUKZ2F0aGVyKG1ldHJpYywgUCwgYmN4SlNELCBtZWRKU0QpIC0+IG1pck9tbmkKYGBgCgpgYGB7cn0KbWlyT21uaQpgYGAKCmBgYHtyfQpOVGF4YUF0TGV2ZWwgJT4lIGJpbmRfcm93cyhzcGVjUm93WywxOjJdKSAlPiUgdW5pdGUobkxldiwgdGF4TGV2ZWxzLCBudGF4YSwgcmVtb3ZlID0gRkFMU0UpIC0+IE5UYXhhQXRMZXZlbDIKTlRheGFBdExldmVsMgpgYGAKCmBgYHtyfQpiaW5kX3Jvd3MoCiAgICBwZXJtS2VyblRhYmxlICU+JSBtdXRhdGUobWV0cmljID0gJ21lZCcpLAogICAgcGVybUtlcm5UYWJsZUdhdXMgJT4lIG11dGF0ZShtZXRyaWMgPSAnYmN4JykKICAgICkgJT4lCmRwbHlyOjpzZWxlY3QodHlwZSwgYW50aWdlbiwgbW9udGgsIG1ldHJpYywgbWlyLlApICU+JQpwYXNzIC0+IFd1ZlBEYXRhCmBgYAoKYGBge3J9CmZpeGFudCA8LSBmdW5jdGlvbihkZil7CiAgICBkZiAlPiUKICAgIG11dGF0ZShhbnRpZ2VuID0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKGFudGlnZW4sICJcXC4iLCAiICIpKSAlPiUKICAgIG11dGF0ZShhbnRpZ2VuID0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKGFudGlnZW4sICJfIiwgIiAiKSkgJT4lCiAgICAjbXV0YXRlKG1ldHJpYyA9ICBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwobWV0cmljLCAiYmN4IiwgIiIpKSAlPiUKICAgIHBhc3MKfQoKZml4c3R1ZmYgPC0gZnVuY3Rpb24oZGYpewogICAgZGYgJT4lCiAgICBmaXhhbnQgJT4lCiAgICBtdXRhdGUobWV0cmljID0gIHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChtZXRyaWMsICJKU0QiLCAiIikpICU+JQogICAgcGFzcwp9CmBgYAoKYGBge3J9Cm1pckRhdCAlPiUgZml4YW50ICU+JSBoZWFkCmBgYAoKYGBge3J9Cm1pckRhdCAlPiUgCiNtdXRhdGUoYW50aWdlbiA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChhbnRpZ2VuLCAiXFwuIiwgIiAiKSkgJT4lIApmaXhzdHVmZiAlPiUKZ2dwbG90KGFlcyh4ID0gbnRheGEsIHkgPSBQLCBjb2wgPSBmYWN0b3IobW9udGgpLCBmaWxsID0gZmFjdG9yKG1vbnRoKSkpICsKZ2VvbV9wb2ludChwY2ggPSAyMSkgKwpmYWNldF9ncmlkKHR5cGUgKyBhbnRpZ2VuIH4gbWV0cmljLCBsYWJlbGxlciA9IGxhYmVsbGVyKGFudGlnZW4gPSBsYWJlbF93cmFwX2dlbih3aWR0aCA9IDEwKSkpICsgCnNjYWxlX3hfbG9nMTAoYnJlYWtzID0gYygzLCBOVGF4YUF0TGV2ZWwyJG50YXhhLCAxMDAwKSwgbGFiZWxzID0gYygib21uaSIsIE5UYXhhQXRMZXZlbDIkbkxldiwgInd1bmlmcmFjIikpICsKc2NhbGVfeV9sb2cxMChicmVha3MgPSBjKDAuMDAyLCAwLjAxLCAwLjA1LCAwLjIsIDEpKSArIApnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MC4wNSwgY29sID0gJ2JsdWUnLCBhbHBoYSA9IDAuNSkgKyBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MC4wMSwgY29sID0gJ3JlZCcsIGFscGhhID0gMC41KSArCiNnZW9tX2hsaW5lKGRhdGEgPSBtaXJPbW5pLCBhZXMoeWludGVyY2VwdCA9IFAsIGNvbCA9IGZhY3Rvcihtb250aCkpKSArCiNhbm5vdGF0aW9uX2xvZ3RpY2tzKHNpZGVzID0gJ2JsJykgKwojZ2VvbV9ydWcoZGF0YSA9IG1pck9tbmksIGFlcyh5ID0gUCwgY29sID0gZmFjdG9yKG1vbnRoKSksIGluaGVyaXQuYWVzID0gRikgKwpnZW9tX3BvaW50KGRhdGEgPSBtaXJPbW5pICU+JSB1bmdyb3VwICU+JSBmaXhzdHVmZiwKICAgICAgICAgICBhZXMoeCA9IDMsIHkgPSBQLCBjb2wgPSBmYWN0b3IobW9udGgpLCBmaWxsID0gZmFjdG9yKG1vbnRoKSksIGluaGVyaXQuYWVzID0gRiwgcGNoID0gMjIsIHNpemUgPSAyKSArCmdlb21fcG9pbnQoZGF0YSA9IFd1ZlBEYXRhICU+JSB1bmdyb3VwICU+JSBmaXhhbnQsCiAgICAgICAgICAgYWVzKHggPSAxMDAwLCB5ID0gbWlyLlAsIGNvbCA9IGZhY3Rvcihtb250aCksIGZpbGwgPSBmYWN0b3IobW9udGgpKSwgaW5oZXJpdC5hZXMgPSBGLCBwY2ggPSAyNCwgc2l6ZSA9IDIpICsKCnNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNiUGFsZXR0ZSkgKyAKc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWFscGhhKGNiUGFsZXR0ZSwgMC41KSkgKyAKdGhlbWVfYncoKSArCnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwsIHZqdXN0ID0gMC41LCBzaXplID0gMTApLAogICAgIHN0cmlwLnRleHQueSA9IChlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpKSAtPiBwanNkMAoKb3B0aW9ucyhyZXByLnBsb3Qud2lkdGg9OCwgcmVwci5wbG90LmhlaWdodD0gNikKcGpzZDAKb3B0aW9ucyhwYXIwKQojIEknZCBsaWtlIHRvIGFkZCB3ZWlnaHRlZCB1bmlmcmFjIGFzIGEgdGljayBtYXJrIG9uIHRoZSByaWdodC4KYGBgCgpgYGB7cn0KTlRheGFBdExldmVsMgpgYGAKCmBgYHtyfQojIE5ldyBjb21iaW5lZCBkYXRhIGZyYW1lIHRoYXQgaGFzIG9tbmlidXMsIHJlZ3VsYXIsIGFuZCB3dW5pZnJhYyBhbGwgaW4gb25lCmJpbmRfcm93cygKICAgICBtaXJEYXQgJT4lIG11dGF0ZShtZXRyaWMgPSAgc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKG1ldHJpYywgIkpTRCIsICIiKSkgJT4lCiAgICBtdXRhdGUodGVzdCA9ICJKU0QiKSwKICAgICBtaXJPbW5pICU+JSB1bmdyb3VwICU+JSBtdXRhdGUobWV0cmljID0gIHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChtZXRyaWMsICJKU0QiLCAiIikpICU+JQogICAgbXV0YXRlKHRheExldmVscyA9ICJPbW5pYnVzIikgJT4lIG11dGF0ZSh0ZXN0ID0gIk9tbmlidXMiKSwKICAgICBXdWZQRGF0YSAlPiUgdW5ncm91cCAlPiUgZHBseXI6OnJlbmFtZShQID0gbWlyLlApICU+JQogICAgbXV0YXRlKHRheExldmVscyA9ICJXVW5pZnJhYyIpICU+JSBtdXRhdGUodGVzdCA9ICJXVW5pZnJhYyIpCikgJT4lIAptdXRhdGUoYW50aWdlbiA9IGZhY3RvcihhbnRpZ2VuLCBsZXZlbHMgPSBjKGFudHMyLCBhbnRzMSwgIkFOWS5FTlYuUFRFRyIpKSkgJT4lCm11dGF0ZSh0eXBlID0gZmFjdG9yKHR5cGUsIGxldmVscyA9IGMoIklnQSIsICJJZ0ciLCAiQ0Q0KyIpKSkgJT4lCm11dGF0ZSh0YXhMZXZlbHMgPSBmYWN0b3IodGF4TGV2ZWxzLCBsZXZlbHMgPSBjKCJPbW5pYnVzIiwgTlRheGFBdExldmVsMiR0YXhMZXZlbHMsICJXVW5pZnJhYyIpKSkgJT4lCmRwbHlyOjpzZWxlY3QoLWMoYmN4SlNET21uaTptZWRKU0RPbW5pKSklPiUKdW5pdGUobkxldiwgdGF4TGV2ZWxzLCBudGF4YSwgcmVtb3ZlID0gRkFMU0UpICU+JQptdXRhdGUobkxldiA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlKG5MZXYsICJfTkEiLCAiIikpICU+JQojbXV0YXRlKGFudGlnZW4gPSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwoYW50aWdlbiwgIlxcLiIsICIgIikpICU+JQojbXV0YXRlKGFudGlnZW4gPSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwoYW50aWdlbiwgIl8iLCAiICIpKSAlPiUKIG11dGF0ZShhbnRpZ2VuID0gZmFjdG9yKGFudGlnZW4sIGxhYmVscyA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChsZXZlbHMoYW50aWdlbiksICJcXC4iLCAiICIpKSkgJT4lCiBtdXRhdGUoYW50aWdlbiA9IGZhY3RvcihhbnRpZ2VuLCBsYWJlbHMgPSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwobGV2ZWxzKGFudGlnZW4pLCAiXyIsICIgIikpKSAlPiUKCgojIG11dGF0ZShhbnRpZ2VuID0gZmFjdG9yKGFudGlnZW4sIGxhYmVscyA9IChsZXZlbHMoYW50aWdlbikpKSkgJT4lCiMgbXV0YXRlKGFudGlnZW4gPSBmYWN0b3IoYW50aWdlbiwgbGFiZWxzID0gKGxldmVscyhhbnRpZ2VuKSkpKSAlPiUKCm11dGF0ZSh0ZXN0ID0gZmFjdG9yKHRlc3QsIGxldmVscyA9IGMoJ09tbmlidXMnLCAnSlNEJywgJ1dVbmlmcmFjJykpKSAlPiUKcGFzcyAtPiBtaXJEYXQyCm1pckRhdDIgJT4lIGZpbHRlcih0eXBlID09ICdDRDQrJykKYGBgCgpgYGB7cn0KbWlyRGF0MiAlPiUgaGVhZApgYGAKCmBgYHtyfQptaXJEYXQyICU+JQpmaWx0ZXIodHlwZSA9PSAiSWdHIikgJT4lCmdncGxvdChhZXMoeCA9IHRheExldmVscywgeSA9IFAsIGNvbCA9IGZhY3Rvcihtb250aCksIGZpbGwgPSBmYWN0b3IobW9udGgpLCBzaGFwZSA9IHRlc3QpKSArCmdlb21fcG9pbnQoc2l6ZSA9IDIpICsKZmFjZXRfZ3JpZCh0eXBlICsgYW50aWdlbiB+IG1ldHJpYywgbGFiZWxsZXIgPSBsYWJlbGxlcihhbnRpZ2VuID0gbGFiZWxfd3JhcF9nZW4od2lkdGggPSAxMCkpKSArIAojc2NhbGVfeF9sb2cxMChicmVha3MgPSBjKDMsIE5UYXhhQXRMZXZlbDIkbnRheGEsIDEwMDApLCBsYWJlbHMgPSBjKCJvbW5pIiwgTlRheGFBdExldmVsMiRuTGV2LCAid3VuaWZyYWMiKSkgKwpzY2FsZV95X2xvZzEwKGJyZWFrcyA9IGMoMC4wMDIsIDAuMDEsIDAuMDUsIDAuMiwgMSkpICsgCmdlb21faGxpbmUoeWludGVyY2VwdD0wLjA1LCBjb2wgPSAnYmx1ZScsIGFscGhhID0gMC41KSArIGdlb21faGxpbmUoeWludGVyY2VwdD0wLjAxLCBjb2wgPSAncmVkJywgYWxwaGEgPSAwLjUpICsKCnNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXMgPSBjKDIyLCAyMSwgMjQpKSArCnNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNiUGFsZXR0ZSkgKyAKc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWFscGhhKGNiUGFsZXR0ZSwgMC41KSkgKyAKdGhlbWVfYncoKSArCnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwsIHZqdXN0ID0gMC41LCBzaXplID0gMTApLAogICAgIHN0cmlwLnRleHQueSA9IChlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpKSAtPiBwanNkCgoKb3B0aW9ucyhyZXByLnBsb3Qud2lkdGg9NiwgcmVwci5wbG90LmhlaWdodD0gOCkKcGpzZAoKZ2dzYXZlKCdmaWd1cmVzL0tlcm5lbFBWc0xldmVsLnBuZycsIHdpZHRoID0gNiwgaGVpZ2h0ID0gOCkKb3B0aW9ucyhwYXIwKQpgYGAKClgtYXhpcyBpcyBub3cgc3BhY2VkIGV2ZW5seQoKVGFibGUgU1guIFAgdmFsdWVzIG9mIGtlcm5lbCByZWdyZXNzaW9uIHRlc3RzLiBDaXJjbGVzIGluZGljYXRlIGplbnNlbiBzaGFubm9uIHZhbHVlcyBhdCBkaWZmZXJlbnQgdGF4b25vbWljIHJlc29sdXRpb25zLiBTcXVhcmVzIGFyZSB0aGUgb21uaWJ1cyBwLXZhbHVlIGZvciB0aGF0IGNvaG9ydCBvZiB0ZXN0cy4gVHJpYW5nbGVzIGluZGljYXRlIGtlcm5lbCByZWdyZXNzaW9uIHAtdmFsdWVzIGZvciB0aGUgY29ycmVzcG9uZGluZyB3ZWlnaHRlZCB1bmlmcmFjIHRlc3QuCgpUaGUgYmx1ZSBhbmQgcmVkIGxpbmVzIGluZGljYXRlIHAgdmFsdWVzIG9mIDUlIGFuZCAxJSByZXNwZWN0aXZlbHkuCgpPYnNlcnZhdGlvbnM6IFRoZSB3ZWlnaHRlZCB1bmlmcmFjIHRlc3QgaXMgc2Vuc2l0aXZlLiBJbiBjYXNlcyB3aGVyZSBvbmx5IG9uZSB0YXhvbmljIGxldmVsIGhpdHMsIHdlaWdodGVkIHVuaWZyYWMgb2Z0ZW4gYWxzbyBmYWxscyBhdCBzb21lIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgdmFsdWUuIFRoZSBvbW5pYnVzIHAgdmFsdWUgaXMgb2Z0ZW4gaGlnaGVyIHRoYW4gdGhlIHdlaWdodGVkIHVuaWZyYWMgb25lLgpXZWlnaHRlZCB1bmlmcmFjIHNlZW1zIGxpa2UgYSBnb29kIHRlc3QgZm9yIGlkZW50aWZ5aW5nIHBhdHRlcm5zIGF0IGFueSBsZXZlbCB0aGF0IHJlbGF0ZSB0byBhbiBvdXRjb21lLiBUaGUgamVuc2VuIHNoYW5ub24gaW5mb3JtcyB1cyBhYm91dCB3aGljaCBsZXZlbCB0aGUgcGF0dGVybiBpcyBvYnNlcnZlZC4KCiMgTG9jYWwgVGVzdHMKCiMjIEZhbWlseSwgZ2VuZXJhIGFuZCBzcGVjaWVzIHZzIHd1ZjEKSSBtaWdodCBldmVuIGJlIGFibGUgdG8gZHJpbGwgZG93biB0byBldmVyeSBsZXZlbC4KCmBgYHtyfQptb2RlbF9lYWNoX3NwZWNpZXMgPC0gZnVuY3Rpb24ocHMsIGYsIHB0aHJlc2ggPSAxLCBxID0gRkFMU0UpewogICAgIyBTdGFydCB3aXRoIHRoZSBvdHUgdGFibGUKcHMgJT4lCiMgcmVzaGFwZSBpdCBzbyB3ZSBoYXZlIGNsciB2YWx1ZXMgZm9yIGV2ZXJ5IHRheG9uLXNhbXBsZSBwYWlyCm90dV90YWJsZSAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCJTYW1wbGUiKSAlPiUgZ2F0aGVyKFRheG9uLCBjbHIsIC1TYW1wbGUpICU+JQogICAgIyBiaW5kIHRoYXQgdG8gdGhlIHNhbXBsZSBkYXRhCiAgICAjIGRvaW5nIHRoaXMgaGVyZSBzZWVtcyByZW1hcmthYmx5IGluZWZmaWNpZW50LCBidXQgaXRzIG5vdCBjcmVhdGluZyBhIGJvdHRsZW5lY2sgc28gSSdsbCBsZWF2ZSBpdC4KbGVmdF9qb2luKAogICAgcHMgJT4lCiAgICAjIHRoZSBzYW1wbGUgZGF0YSBuZWVkIHRvIGhhdmUgTURTMSBhbmQgTURTMiBhcHBlbmRlZCB0byB0aGVtCiAgICBwaHlsb19qb2luKAogICAgcHNOMi5wY29hICU+JSBzY29yZXMoZGlzcGxheSA9ICJzaXRlcyIpICU+JSAjIGhhcmRjb2RlZCBwc04yLnBjb2EKICAgICAgICBhcy5kYXRhLmZyYW1lICU+JSAKICAgICAgICByb3duYW1lc190b19jb2x1bW4gJT4lIAogICAgICAgIGRwbHlyOjpzZWxlY3QoJ3Jvd25hbWUnLCAnTURTMScsICdNRFMyJyksCiAgICBieSA9ICdyb3duYW1lJwopICU+JQogICAgIyBiYWNrIHRvIGJpbmRpbmcgdG8gc2FtcGxlIGRhdGEKICAgIHNhbXBsZV9kYXRhICU+JSBhcygnZGF0YS5mcmFtZScpICU+JSByb3duYW1lc190b19jb2x1bW4oIlNhbXBsZSIpLAogICAgIGJ5ID0gJ1NhbXBsZScpICU+JQoKZ3JvdXBfYnkoVGF4b24pICU+JSAgIyBncm91cCBhbmQgbmVzdCBmb3IgbW9kZWwgcnVuCm5lc3QgJT4lCm11dGF0ZShNb2QgPSBtYXAoZGF0YSwgZikpICU+JSAjIGFwcGx5IG1vZGVsIG92ZXIgZWFjaCBzcGVjaWVzCm11dGF0ZShHbGFuY2UgPSBtYXAoTW9kLCBnbGFuY2UpLCBUaWR5ID0gbWFwKE1vZCwgdGlkeSkpICU+JSAjIGV4dHJhY3QgcmVsZXZhbnQgZGF0YSBmcm9tIG1vZGVsCiMgdmlldyBtb2RlbApkcGx5cjo6c2VsZWN0KFRheG9uLCBUaWR5KSAlPiUgdW5uZXN0ICU+JQptdXRhdGUodGVybSA9IGdzdWIoJ1tcXCggXFwpXScsJycsIHRlcm0pKSAlPiUgIyByZW1vdmUgcGFyZW50aGVzZXMgZnJvbSAiKEludGVyY2VwdCkiCmdhdGhlcihtZWFzLCB2YWwsIGVzdGltYXRlOnAudmFsdWUpICU+JSAKdW5pdGUobWVhcywgdGVybSwgbWVhcykgJT4lIHNwcmVhZChtZWFzLCB2YWwpICU+JSBhcnJhbmdlKGNscl9lc3RpbWF0ZSkgJT4lIApkcGx5cjo6c2VsZWN0KFRheG9uLCBJbnRlcmNlcHRfZXN0aW1hdGUsIGNscl9lc3RpbWF0ZSwgY2xyX3N0ZC5lcnJvciwgY2xyX3AudmFsdWUpICU+JQogICAgIyBhZGQgcSB2YWx1ZQogICAge2lmKHEpIG11dGF0ZSguLCBjbHJfcS52YWx1ZSA9IHAycShjbHJfcC52YWx1ZSkpIGVsc2UgLn0gJT4lCiAgICAKIGZpbHRlcihjbHJfcC52YWx1ZSA8IHB0aHJlc2gpICU+JQoKICAgICAjSm9pbiB0YXhvbm9teSBpbmZvcm1hdGlvbgogICAgIGxlZnRfam9pbigKICAgICBhcy5kYXRhLmZyYW1lKHBzQHRheF90YWJsZUAuRGF0YSkgJT4lIGFzLnRpYmJsZSAlPiUgZHBseXI6OnNlbGVjdChLaW5nZG9tOkdlbnVzLCBTcGVjaWVzLCB0YWcpICU+JQogICAgICAgICBtdXRhdGUodGFnID0gYXMuY2hhcmFjdGVyKHRhZykpLCAjIG11dGF0ZSBzbyB0YWcgaXMgYW5kIHRheG9uIGFyZSBib3RoIGNoYXJhY3RlciBjbGFzcwogICAgIGJ5ID0gYygiVGF4b24iID0gInRhZyIpKSAlPiUKcGFzcwogfQpgYGAKCmBgYHtyfQptb2RlbF9lYWNoX3NwZWNpZXNfZm9yX2FudGlnZW4gPC0gZnVuY3Rpb24oYW50aWdlbiwgcHMgPSBwc04yKXsKICAgIHBzICU+JQogICAgbW9kZWxfZWFjaF9zcGVjaWVzKGZ1bmN0aW9uKGRmKXtnbG0obWVkY29kZTIoZ2V0KGFudGlnZW4pKSB+IGNsciwgZGF0YSA9IGRmLCBmYW1pbHkgPSAnYmlub21pYWwnKX0sIHEgPSBUUlVFLCBwdGhyZXNoID0gMSkKfQpgYGAKCmBgYHtyfQpDb2xzVG9SdW4gPC0gYygnSWdHX0Nvbi42LmdwMTIwLkJfTW9udGhfNi41JywgJ0lnR19Db24uNi5ncDEyMC5CX01vbnRoXzEyJywgJ0lnR19ncDQxX01vbnRoXzAnLCAnSWdHX2dwNDFfTW9udGhfNi41JywgJ0lnR19ncDcwX0IuQ2FzZUFfVjFfVjJfTW9udGhfMTInLCAnSWdHX1pNOTYuZ3AxNDBfTW9udGhfNi41JywgJ01EUzEnLCAnaXNNYWxlJyApIAoKYGBgCgpgYGB7cn0KbW9kZWxfZWFjaF9zcGVjaWVzX2Nhc2UgPC0gZnVuY3Rpb24ocHMpewogICAgCiAgICBwcyAlPiUgbW9kZWxfZWFjaF9zcGVjaWVzKGZ1bmN0aW9uKGRmKXtnbG0oTURTMSB+IGNsciwgZGF0YSA9IGRmLCBmYW1pbHkgPSAnZ2F1c3NpYW4nKX0sIHEgPSBUUlVFLCBwdGhyZXNoID0gMSkgJT4lCmFycmFuZ2UoY2xyX2VzdGltYXRlKSAlPiUKbXV0YXRlKFRheG9uID0gZmFjdG9yKFRheG9uLCBsZXZlbHMgPSBUYXhvbltvcmRlcihjbHJfZXN0aW1hdGUpXSkpICU+JQogICAgbXV0YXRlKHRlc3QgPSAnZ2F1c3NpYW4nLCBhbnRpZ2VuID0gJ01EUzEnKSAlPiUKcGFzcyAtPiBsb2NfbWRzMUdsbXMKICAgIAogICAgICAgIHBzICU+JSBtb2RlbF9lYWNoX3NwZWNpZXMoZnVuY3Rpb24oZGYpe2dsbShsb2cxMChJZ0dfQ29uLjYuZ3AxMjAuQl9Nb250aF8xMiArIDEwMCkgfiBjbHIsIGRhdGEgPSBkZiwgZmFtaWx5ID0gJ2dhdXNzaWFuJyl9LCBxID0gVFJVRSwgcHRocmVzaCA9IDEpICU+JQphcnJhbmdlKGNscl9lc3RpbWF0ZSkgJT4lCiBtdXRhdGUoVGF4b24gPSBmYWN0b3IoVGF4b24sIGxldmVscyA9IGxldmVscyhsb2NfbWRzMUdsbXMkVGF4b24pKSkgJT4lCiAgICBtdXRhdGUodGVzdCA9ICdnYXVzc2lhbicsIGFudGlnZW4gPSAnQ29uLjYuZ3AxMjAuQl9Nb250aF8xMicpICU+JQogcGFzcyAtPiBsb2NfZ3AxMjBHbG1zCiAgICAKICAgICAgdGliYmxlKGFudGlnZW4gPSBDb2xzVG9SdW4pICU+JSBtdXRhdGUobW9kZWwgPSBtYXAoYW50aWdlbiwgfm1vZGVsX2VhY2hfc3BlY2llc19mb3JfYW50aWdlbiguLCBwcyA9IHBzKSkpICU+JQogIHVubmVzdCAlPiUgbXV0YXRlKFRheG9uID0gZmFjdG9yKFRheG9uLCBsZXZlbHMgPSBsZXZlbHMobG9jX21kczFHbG1zJFRheG9uKSkpICU+JQogICAgICAgIG11dGF0ZSh0ZXN0ID0gJ2Jpbm9taWFsJykgJT4lCiAgICBwYXNzLT4gbG9jX2xvZ2l0Q29lZnMKICAgIAoKICAgICNsaXN0KGxvY19tZHMxR2xtcywgbG9jX2dwMTIwR2xtcywgbG9jX2xvZ2l0Q29lZnMpCiAgICAgYmluZF9yb3dzKGxvY19tZHMxR2xtcywgbG9jX2dwMTIwR2xtcywgbG9jX2xvZ2l0Q29lZnMpICU+JSBkcGx5cjo6c2VsZWN0KHRlc3QsIGFudGlnZW4sIGV2ZXJ5dGhpbmcoKSkKICAgIAp9CmBgYAoKYGBge3J9CiNwc0RmICU+JSBtdXRhdGUocHMyID0gbWFwKHBzLCB+c3dhcC5waHlsb3NlcS50YXhuYW1lcyh0YWdfcGh5bG9zZXEocmVtb3ZlX3RhZ19waHlsb3NlcSguKSkpKSkgLT4gdGVzdApgYGAKCmBgYHtyfQojcHNEZltbMSwiY2xyIl1dICU+JSB0YXhfdGFibGUKYGBgCgpgYGB7cn0KcHNEZltbMV1dCmBgYAoKYGBge3J9CnBzRGYgJT4lIHByaW50CmBgYAoKYGBge3J9CnB0bSA9IHByb2MudGltZSgpCnBzRGYgJT4lIGRwbHlyOjpzZWxlY3QodGF4TGV2ZWxzLCBudGF4YSwgY2xyKSAlPiUgbXV0YXRlKGxvY2FsbW9kID0gbWFwKGNsciwgbW9kZWxfZWFjaF9zcGVjaWVzX2Nhc2UpKSAtPnBzRGZMb2MKcHJvYy50aW1lKCkgLSBwdG0KYGBgCgpgYGB7cn0KcHJpbnQocHNEZkxvYykKYGBgCgpgYGB7cn0KcHNEZkxvYyAlPiUgZHBseXI6OnNlbGVjdCgtY2xyKSAlPiUgdW5uZXN0KGxvY2FsbW9kKSAtPiB0bXAKYGBgCgpgYGB7cn0KcHNEZkxvYyR0YXhMZXZlbHMKYGBgCgpgYGB7cn0KdG1wICU+JSBtdXRhdGUodGF4TGV2ZWxzID0gZmFjdG9yKHRheExldmVscywgbGV2ZWxzID0gcHNEZkxvYyR0YXhMZXZlbHMpKSAtPiBMb2NhbFRlc3RzCmBgYAoKYGBge3J9CkxvY2FsVGVzdHMgJT4lIApmaWx0ZXIoYW50aWdlbiAhPSAiTURTMSIpICU+JQp3cml0ZV9jc3YoInRhYmxlcy9BbGxMb2NhbFRlc3RzLmNzdiIpCmBgYAoKIyMgSSB3YW50IHRvIHNob3cgdGhlIGxvY2FsIHRlc3RzIHZzIGFudGlib2RpZXMuCgpgYGB7cn0KTG9jYWxUZXN0cyAlPiUgaGVhZApgYGAKCmBgYHtyfQpvcHRpb25zKHJlcHIucGxvdC53aWR0aD02LCByZXByLnBsb3QuaGVpZ2h0PTgpCkxvY2FsVGVzdHMgJT4lIApmaWx0ZXIoYW50aWdlbiAhPSAiTURTMSIpICU+JQojQ2xlYW4gdXAgbGFiZWxzCm11dGF0ZShhbnRpZ2VuID0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKGFudGlnZW4sICJfIiwgIiAiKSkgJT4lCm11dGF0ZShhbnRpZ2VuID0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKGFudGlnZW4sICIgTW9udGgiLCAiIC0tIE1vbnRoIikpICU+JQptdXRhdGUoYW50aWdlbiA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChhbnRpZ2VuLCAiIElnRyAiLCAiIikpICU+JQpnZ3Bsb3QoYWVzKHggPSBmYWN0b3IodGF4TGV2ZWxzKSwgeSA9IGNscl9xLnZhbHVlKSkgKyBnZW9tX3BvaW50KHNpemUgPSAwLjEpICsKZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC4yLCBjb2xvciA9ICdncmV5JykgKwogZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC4wNSwgY29sb3IgPSAnYmx1ZScpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC4wMSwgY29sb3IgPSAnZ3JlZW4nKSArCmZhY2V0X3dyYXAofmFudGlnZW4gKyB0ZXN0KSArIHNjYWxlX3lfbG9nMTAoKSArCnRoZW1lX2J3KCkgKwp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41KSkKCmdnc2F2ZSgnZmlndXJlcy9Mb2NhbFFFdmVyeUxldmVsLnBuZycpCmBgYAoKYGBge3J9Cm9wdGlvbnMocmVwci5wbG90LndpZHRoPTYsIHJlcHIucGxvdC5oZWlnaHQ9OCkKTG9jYWxUZXN0cyAlPiUgCmZpbHRlcihhbnRpZ2VuICE9ICJNRFMxIikgJT4lCiNDbGVhbiB1cCBsYWJlbHMKbXV0YXRlKGFudGlnZW4gPSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwoYW50aWdlbiwgIl8iLCAiICIpKSAlPiUKbXV0YXRlKGFudGlnZW4gPSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwoYW50aWdlbiwgIiBNb250aCIsICIgLS0gTW9udGgiKSkgJT4lCm11dGF0ZShhbnRpZ2VuID0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKGFudGlnZW4sICIgSWdHICIsICIiKSkgJT4lCmdncGxvdChhZXMoeCA9IGZhY3Rvcih0YXhMZXZlbHMpLCB5ID0gY2xyX3AudmFsdWUpKSArIGdlb21fcG9pbnQoc2l6ZSA9IDAuMSkgKwpnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjIsIGNvbG9yID0gJ2dyZXknKSArCiBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjA1LCBjb2xvciA9ICdibHVlJykgKyBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjAxLCBjb2xvciA9ICdncmVlbicpICsKZmFjZXRfd3JhcCh+YW50aWdlbiArIHRlc3QpICsjIHNjYWxlX3lfbG9nMTAoKSArCnRoZW1lX2J3KCkgKwp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41KSkKZ2dzYXZlKCdmaWd1cmVzL0xvY2FsUEV2ZXJ5TGV2ZWwucG5nJykKYGBgCgpgYGB7cn0Kb3B0aW9ucyhyZXByLnBsb3Qud2lkdGg9NiwgcmVwci5wbG90LmhlaWdodD04KQpMb2NhbFRlc3RzICU+JSAKZmlsdGVyKGFudGlnZW4gIT0gIk1EUzEiKSAlPiUKI0NsZWFuIHVwIGxhYmVscwptdXRhdGUoYW50aWdlbiA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChhbnRpZ2VuLCAiXyIsICIgIikpICU+JQptdXRhdGUoYW50aWdlbiA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChhbnRpZ2VuLCAiIE1vbnRoIiwgIiAtLSBNb250aCIpKSAlPiUKbXV0YXRlKGFudGlnZW4gPSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwoYW50aWdlbiwgIiBJZ0cgIiwgIiIpKSAlPiUKZ2dwbG90KGFlcyh4ID0gZmFjdG9yKHRheExldmVscyksIHkgPSBjbHJfcC52YWx1ZSkpICsgZ2VvbV9wb2ludChzaXplID0gMC4xKSArCmdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuMiwgY29sb3IgPSAnZ3JleScpICsKIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuMDUsIGNvbG9yID0gJ2JsdWUnKSArIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuMDEsIGNvbG9yID0gJ2dyZWVuJykgKwpmYWNldF93cmFwKH5hbnRpZ2VuICsgdGVzdCkgKyBzY2FsZV95X2xvZzEwKCkgKwp0aGVtZV9idygpICsKdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IDAuNSkpCmdnc2F2ZSgnZmlndXJlcy9Mb2NhbFBFdmVyeUxldmVsX0xvZ1NjYWxlLnBuZycpCmBgYAoKYGBge3J9Cm9yZGVyX3RheGFfYnlfbWRzMSA8LSBmdW5jdGlvbihkZil7CiAgICAjIHRoaXMgaGFzIHRvIGJlIGEgbW9kZWxfZWFjaF9zcGVjaWVzIHR5cGUgb2YgZGF0YSBmcmFtZQogICAgZGYgJT4lIGZpbHRlcihhbnRpZ2VuID09ICdNRFMxJyAmIHRlc3QgPT0gJ2dhdXNzaWFuJykgJT4lCiAgICBtdXRhdGUoVGF4b25GID0gZmFjdG9yKFRheG9uLCBsZXZlbHMgPSBUYXhvbltvcmRlcihjbHJfZXN0aW1hdGUpXSkpIC0+IG1kczFkZgogICAgZGYgJT4lIG11dGF0ZShUYXhvbkYgPSBmYWN0b3IoVGF4b24sIGxldmVscyA9IGxldmVscyhtZHMxZGYkVGF4b25GKSkpCn0KYGBgCgpUbyBteSBhbm5veWFuY2UsIGV2ZXJ5dGhpbmcgaXMgbGFiZWxlZCB3aXRoIElnRyBleGNlcHQgZ3AxMjBfMTIKCmBgYHtyfQpMb2NhbFRlc3RzICU+JQpmaWx0ZXIodGF4TGV2ZWxzID09ICdGYW1pbHknKSAlPiUKb3JkZXJfdGF4YV9ieV9tZHMxICU+JQoKZmlsdGVyKAogICAgKGFudGlnZW4gJWluJSBjKCdJZ0dfZ3A0MV9Nb250aF8wJywgJ0lnR19ncDQxX01vbnRoXzYuNScsICdJZ0dfQ29uLjYuZ3AxMjAuQl9Nb250aF82LjUnKSl8CiAgICAoYW50aWdlbiA9PSAnQ29uLjYuZ3AxMjAuQl9Nb250aF8xMicgJiB0ZXN0ID09ICdnYXVzc2lhbicpCiAgICAgICkgJT4lCm11dGF0ZShhbnRpZ2VuID0gZmFjdG9yKGFudGlnZW4sCiAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoJ0lnR19ncDQxX01vbnRoXzAnLCAnSWdHX2dwNDFfTW9udGhfNi41JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnSWdHX0Nvbi42LmdwMTIwLkJfTW9udGhfNi41JywgJ0Nvbi42LmdwMTIwLkJfTW9udGhfMTInKSkpICU+JQpmaWx0ZXIoY2xyX3AudmFsdWUgPCAwLjA1ICYgY2xyX3EudmFsdWUgPCAwLjIpICU+JQpkcGx5cjo6c2VsZWN0KGFudGlnZW46Y2xyX2VzdGltYXRlKSAlPiUKZHBseXI6OnNlbGVjdCgtSW50ZXJjZXB0X2VzdGltYXRlKSAlPiUKbXV0YXRlKGNvcmRpciA9IHNpZ24oY2xyX2VzdGltYXRlKSkgJT4lCnBhc3MKYGBgCgpgYGB7cn0KTG9jYWxUZXN0cyAlPiUgcHVsbChhbnRpZ2VuKSAlPiUgdW5pcXVlCmBgYAoKYGBge3J9CiMgRmFtaWx5IEhpdHMKTG9jYWxUZXN0cyAlPiUKZmlsdGVyKHRheExldmVscyA9PSAnRmFtaWx5JykgJT4lCm9yZGVyX3RheGFfYnlfbWRzMSAlPiUKCmZpbHRlcigKICAgIChhbnRpZ2VuICVpbiUgYygnSWdHX2dwNDFfTW9udGhfMCcsICdJZ0dfZ3A0MV9Nb250aF82LjUnLCAnSWdHX0Nvbi42LmdwMTIwLkJfTW9udGhfNi41JywgCiAgICAgICAgICAgICAgICAgICAgJ0lnR19aTTk2LmdwMTQwX01vbnRoXzYuNScsJ0lnR19ncDcwX0IuQ2FzZUFfVjFfVjJfTW9udGhfMTInKSl8CiAgICAoYW50aWdlbiA9PSAnQ29uLjYuZ3AxMjAuQl9Nb250aF8xMicgJiB0ZXN0ID09ICdnYXVzc2lhbicpCiAgICAgICkgJT4lCm11dGF0ZShhbnRpZ2VuID0gZmFjdG9yKGFudGlnZW4sIGxldmVscyA9IGMoCiAgICAnSWdHX2dwNDFfTW9udGhfMCcsICdJZ0dfZ3A0MV9Nb250aF82LjUnLCAnSWdHX0Nvbi42LmdwMTIwLkJfTW9udGhfNi41JywgJ0Nvbi42LmdwMTIwLkJfTW9udGhfMTInLAogICAgJ0lnR19aTTk2LmdwMTQwX01vbnRoXzYuNScsJ0lnR19ncDcwX0IuQ2FzZUFfVjFfVjJfTW9udGhfMTInCikpKSAlPiUKCnBhc3MgLT4gdG1wCgojdG1wJGFudGlnZW4gJT4lIHVuaXF1ZQoKdG1wICU+JSBmaWx0ZXIoY2xyX3AudmFsdWUgPCAwLjA1ICYgY2xyX3EudmFsdWUgPCAwLjIpICU+JQpwdWxsKFRheG9uKSAlPiUgdW5pcXVlIC0+IHVzZUZhbWlseQoKdG1wICU+JSBmaWx0ZXIoVGF4b24gJWluJSB1c2VGYW1pbHkpICU+JQoKI0NsZWFuIHVwIGxhYmVscwptdXRhdGUoYW50aWdlbiA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChhbnRpZ2VuLCAiXyIsICIgIikpICU+JQptdXRhdGUoYW50aWdlbiA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChhbnRpZ2VuLCAiIE1vbnRoIiwgIiAtLSBNb250aCIpKSAlPiUKbXV0YXRlKGFudGlnZW4gPSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwoYW50aWdlbiwgIklnRyAiLCAiIikpICU+JQoKZ2dwbG90KGFlcyh4ID0gVGF4b25GLCB5ID0gY2xyX2VzdGltYXRlLAogICAgICAgICAgIGNvbG9yID0gKGNscl9wLnZhbHVlIDwgMC4wNSksIHNoYXBlID0oY2xyX3EudmFsdWUgPCAwLjIpKSkgKwpnZW9tX3BvaW50KHNpemUgPSAzKSArIApnZW9tX2Vycm9yYmFyKGFlcyh5bWluID0gY2xyX2VzdGltYXRlIC0gMipjbHJfc3RkLmVycm9yLCB5bWF4ID0gY2xyX2VzdGltYXRlICsgMipjbHJfc3RkLmVycm9yKSkgKyAKdGhlbWVfYncoKSArCnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCkgKwpmYWNldF93cmFwKH5hbnRpZ2VuLCBuY29sID0gMSwgc2NhbGVzID0gJ2ZyZWVfeScpICsgeGxhYigiRmFtaWx5IExldmVsIFRheG9uIikKIyBTaG93IHRoZSBjZW5zb3JlZCBvbmVzIGFjY3Jvc3MgLSBzbyB0aGlzIHdvdWxkIGJlIGV2ZXJ5dGhpbmcgd2l0aCBhdCBsZWFzdCBvbmUgaGl0CiMgYnV0IGFsc28gc2hvdyB3aGF0IHRoZXkgYXJlIGluIGFsbCBjYXNlcy4KCmdnc2F2ZSgnZmlndXJlcy9hbnlGYW1pbHlJZ2cucG5nJywgd2lkdGggPSA2LCBoZWlnaHQgPSA4KQpgYGAKCkkgdGhpbmsgaXRzIHdvcnRoIGRpZ2dpbmcgaW50byBjbG9zdHJpZGlhIGFuZCBQcm9waHlyb21vbmlkYWNlYWUgd2l0aCBzdGFja2VkIGJhcnMKCiMgUHJvcG9ydGlvbmFsaXR5IGhlYXRtYXAKCkZhbWlseSBsZXZlbAoKTGV0cyBjb21lIGJhY2sgdG8gdGhpcyBhZnRlciB3ZSd2ZSBkb25lIHRoZSBsb2NhbCB0ZXN0cy4gU2luY2Ugd2UgbmVlZCB0aGVtIHRvIGNvbG9yIGNvZGUgdGhlIGF4ZXMuCgpgYGB7cn0KcHNEZiAlPiUgcHJpbnQKYGBgCgpgYGB7cn0KcHNEZiAlPiUgZmlsdGVyKHRheExldmVscyA9PSAnRmFtaWx5JykgJT4lIGRwbHlyOjpzZWxlY3QocHMpICU+JSBwdWxsICU+JS5bWzFdXQpgYGAKCmBgYHtyfQpwcmludChwc0RmKQpgYGAKCmBgYHtyfQojbkZhbWlseVRheGEgPC0gTlRheGFBdExldmVsICU+JSBmaWx0ZXIodGF4TGV2ZWxzID09ICdGYW1pbHknKSAlPiUgcHVsbChudGF4YSkKCnBzRGYgJT4lIGZpbHRlcih0YXhMZXZlbHMgPT0gJ0ZhbWlseScpICU+JSBkcGx5cjo6c2VsZWN0KHBzTm9aZXJvKSAlPiUgcHVsbCAlPiUuW1sxXV0gJT4lCm90dV90YWJsZSAlPiUgYXMuZGF0YS5mcmFtZSAlPiUKcGFzcyAtPiBteVJlbAoKcHRtID0gcHJvYy50aW1lKCkKcGhpQm9vdCA8LSBib290KGRhdGEgPSBteVJlbCwgc3RhdGlzdGljID0gYm9vdF9waGksIFIgPSAxMDAwKQpwcm9jLnRpbWUoKSAtIHB0bQoKcHRtID0gcHJvYy50aW1lKCkKdGlkeUNJIDwtIHVud2FybigKICAgIHRpZHkocGhpQm9vdCxjb25mLmludD1UUlVFLGNvbmYubWV0aG9kPSJiY2EiKQogICAgKQpwcm9jLnRpbWUoKSAtIHB0bQoKbXlSZWwgJT4lIG1ha2VfcHJvcG9ydGlvbmFsaXR5X21hdHJpeCAlPiUgCiAgICAgICAgIGFzLmRhdGEuZnJhbWUgJT4lCiAgICAgICAgIHJvd25hbWVzX3RvX2NvbHVtbigiVGF4b25YIikgJT4lIGdhdGhlcihUYXhvblksIHBoaSwgLVRheG9uWCkgJT4lCiAgICBmaWx0ZXIoVGF4b25YICE9IFRheG9uWSkgJT4lIGRhdGEuZnJhbWUodGlkeUNJKSAtPiBuYW1lZFRpZHlDSQpgYGAKCmBgYHtyfQpoZWFkKExvY2FsVGVzdHMpCmBgYAoKYGBge3J9CkxvY2FsVGVzdHMgJT4lIGZpbHRlcih0ZXN0ID09ICdnYXVzc2lhbicgJgogICAgICAgICAgICAgICAgICAgICAgICBhbnRpZ2VuID09ICdNRFMxJyAmCiAgICAgICAgICAgICAgICAgICAgICAgICBjbHJfcC52YWx1ZSA8MC4wNSAmCiAgICAgICAgICAgICAgICAgICAgICAgIGNscl9xLnZhbHVlIDwgMC4yJgogICAgICAgICAgICAgICAgICAgICAgICB0YXhMZXZlbHMgPT0gIkZhbWlseSIpIC0+IHRtcAp0bXAgJT4lIHB1bGwoVGF4b24pIC0+IE1EUzFGYW0KdG1wICU+JSBmaWx0ZXIoY2xyX2VzdGltYXRlIDwgMCkgJT4lIHB1bGwoVGF4b24pIC0+IGxvd01EUzFGYW0KdG1wICU+JSBmaWx0ZXIoY2xyX2VzdGltYXRlID49IDApICU+JSBwdWxsKFRheG9uKSAtPiBoaWdoTURTMUZhbQpgYGAKCmh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQ4NTMxOTg3L2luY29ycG9yYXRlLW1vcmUtaW5mb3JtYXRpb24tYWJvdXQtdmFyaWFibGVzLW9uLWF4ZXMtaW50by1hLWhlYXRtYXAtaW4tZ2dwbG90LzQ4NTMyOTgzIzQ4NTMyOTgzCgpJJ2QgbGlrZSB0byBkbyB0aGlzLCBidXQgZm9yIGdwNDEgYmFzZWxpbmUgYW5kIGdwMTIwIGFzIHdlbGwuCgpgYGB7cn0KdXNlRmFtaWx5CmBgYAoKYGBge3J9CkxvY2FsVGVzdHMgJT4lIGhlYWQKYGBgCgpgYGB7cn0KcmVzaGFwZTI6Om1lbHQKYGBgCgpgYGB7cn0KdGFyZ1N0YXQgPC0gInBoaSIKbmFtZWRUaWR5Q0kgJT4lIGRwbHlyOjpzZWxlY3QoVGF4b25YLCBUYXhvblksIHRhcmdTdGF0KSAlPiUgc3ByZWFkKGtleSA9IFRheG9uWSwgdmFsdWUgPSB0YXJnU3RhdCkgJT4lCnJlbW92ZV9yb3duYW1lcyAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCJUYXhvblgiKSAlPiUKYXMuZGlzdCAlPiUgYXMubWF0cml4IC0+IHBoaWRhdGEKCnBoaV9kZCA8LSBhcy5kaXN0KHBoaWRhdGEpCnBoaV9oYyA8LSBoY2x1c3QocGhpX2RkKQoKcGhpZGF0YSAlPiUKIy5bcGhpX2hjJG9yZGVyLCBwaGlfaGMkb3JkZXJdICU+JSAjIHRoaXMgd2F5IGFsc28gd29ya2VkIGp1c3QgZmluZQpyZXNoYXBlMjo6bWVsdCgpICU+JQptdXRhdGUoVmFyMSA9IGZhY3RvcihWYXIxLCBsZXZlbHMgPSB1bmlxdWUocGhpX2hjJGxhYmVscylbcGhpX2hjJG9yZGVyXSkpICU+JQptdXRhdGUoVmFyMiA9IGZhY3RvcihWYXIyLCBsZXZlbHMgPSB1bmlxdWUocGhpX2hjJGxhYmVscylbcGhpX2hjJG9yZGVyXSkpICU+JQpwYXNzLT4gdG1wCgpwX3BoaV8xIDwtIGdncGxvdCh0bXAsIGFlcyhWYXIxLCBWYXIyLCBmaWxsID0odmFsdWUpKSkgKyAKZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQoaGlnaCA9ICJncmV5OTAiLCBsb3cgPSAicmVkIiwgCiAgICBzcGFjZSA9ICJMYWIiLCAKICAgIG5hbWU9InBoaSIsCiAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYyhOQSwgMyksIG5hLnZhbHVlID0gIndoaXRlIikgKwojICB0aGVtZV9taW5pbWFsKCkrICMgbWluaW1hbCB0aGVtZQogdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoCiAgICAgYW5nbGUgPSA5MCwgdmp1c3QgPSAxLCBzaXplID0gNywgaGp1c3QgPSAxLAogICAgIGZhY2UgPSBpZmVsc2UobGV2ZWxzKHRtcCRWYXIxKSAlaW4lIHVzZUZhbWlseSwgImJvbGQiLCAicGxhaW4iKSwKICAgICBjb2xvdXIgPSBpZmVsc2UobGV2ZWxzKHRtcCRWYXIxKSAlaW4lIHVzZUZhbWlseSwgImJsYWNrIiwgImdyZXkzMCIpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dCgKICAgICAgICAgICBzaXplID0gNywKICAgICAgICAgICBmYWNlID0gaWZlbHNlKGxldmVscyh0bXAkVmFyMSkgJWluJSBNRFMxRmFtLCAiYm9sZCIsICJwbGFpbiIpLAogICAgICAgICAgIGNvbG91ciA9IGlmZWxzZShsZXZlbHModG1wJFZhcjEpICVpbiUgbG93TURTMUZhbSwgInJlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGxldmVscyh0bXAkVmFyMSkgJWluJSBoaWdoTURTMUZhbSwgImJsdWUiLCAiZ3JleTMwIikpCiAgICAgICApKSsKIGNvb3JkX2ZpeGVkKCkgKwpsYWJzKHggPSAiRmFtaWx5IEFueSBJZ2ciLHkgPSAiRmFtaWx5IE1EUzEgKHJlZC1sb3csIGJsdWUtaGlnaCkiICkgKwojIHJlY3RhbmdsZXMgYXJvdW5kIHRoZSB0aHJlZSBjbHVzdGVycywgcG9zaXRpb25lZCBieSBleWUKICBnZW9tX3JlY3QoYWVzKHhtaW4gPSAwICsgMC41LCB4bWF4ID0gMTAgLSAwLjUsIHltaW4gPSAwICsgMC41LCB5bWF4ID0gMTAgLSAwLjUpLAogICAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3IgPSAiZ3JheTIwIiwgc2l6ZSA9IDEuNSkgKwogIGdlb21fcmVjdChhZXMoeG1pbiA9IDEzICsgMC41LCB4bWF4ID0gMjQgLSAwLjUsIHltaW4gPSAxMyArIDAuNSwgeW1heCA9IDI0IC0gMC41KSwKICAgICAgICAgICAgICAgZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG9yID0gImdyYXkyMCIsIHNpemUgPSAxLjUpICsKICBnZW9tX3JlY3QoYWVzKHhtaW4gPSAyMyArIDAuNSwgeG1heCA9IDM3IC0gMC41LCB5bWluID0gMjMgKyAwLjUsIHltYXggPSAzNyAtIDAuNSksCiAgICAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvciA9ICJncmF5MjAiLCBzaXplID0gMS41KQoKCnBfcGhpXzEKIyBnZ3NhdmUoImZpZ3VyZXMvcGhpX3ZzX21kczFfYW5kX2lnZy5wbmciLCBwX3BoaV8xLCB3aWR0aCA9IDYsIGhlaWdodCA9IDYpCmBgYAoKYGBge3J9CkxvY2FsVGVzdHMgJT4lCmZpbHRlcih0YXhMZXZlbHMgPT0gJ0ZhbWlseScpICU+JQpvcmRlcl90YXhhX2J5X21kczEgJT4lCgpmaWx0ZXIoCiAgICAoYW50aWdlbiAlaW4lIGMoJ0lnR19ncDQxX01vbnRoXzAnLCAnSWdHX2dwNDFfTW9udGhfNi41JywgJ0lnR19Db24uNi5ncDEyMC5CX01vbnRoXzYuNScsCiAgICAgICAgICAgICAgICAgICAnSWdHX1pNOTYuZ3AxNDBfTW9udGhfNi41JywnSWdHX2dwNzBfQi5DYXNlQV9WMV9WMl9Nb250aF8xMicpKXwKICAgIChhbnRpZ2VuID09ICdDb24uNi5ncDEyMC5CX01vbnRoXzEyJyAmIHRlc3QgPT0gJ2dhdXNzaWFuJykKICAgICAgKSAlPiUKbXV0YXRlKGFudGlnZW4gPSBmYWN0b3IoYW50aWdlbiwgbGV2ZWxzID0gYygnSWdHX2dwNDFfTW9udGhfMCcsICdJZ0dfZ3A0MV9Nb250aF82LjUnLCAnSWdHX0Nvbi42LmdwMTIwLkJfTW9udGhfNi41JywgJ0Nvbi42LmdwMTIwLkJfTW9udGhfMTInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0lnR19aTTk2LmdwMTQwX01vbnRoXzYuNScsJ0lnR19ncDcwX0IuQ2FzZUFfVjFfVjJfTW9udGhfMTInKSkpICU+JQpwYXNzIC0+IHRtcAoKdG1wICU+JSBkcGx5cjo6c2VsZWN0KGFudGlnZW4sIFRheG9uLCBjbHJfZXN0aW1hdGUsIGNscl9wLnZhbHVlLCBjbHJfcS52YWx1ZSkgJT4lCm11dGF0ZShjbHJfc2lnbiA9IHNpZ24oY2xyX2VzdGltYXRlKSkgJT4lCm11dGF0ZShpc0hpdCA9IGlmZWxzZShjbHJfcC52YWx1ZSA8IDAuMDUgJiBjbHJfcS52YWx1ZSA8IDAuMiwgMSwgMCkpICU+JQptdXRhdGUoVGF4b24gPSBmYWN0b3IoVGF4b24sIGxldmVscyA9IHVuaXF1ZShwaGlfaGMkbGFiZWxzKVtwaGlfaGMkb3JkZXJdKSkgJT4lCnBhc3MgLT4gY2hvcmRkYXRhCmBgYAoKYGBge3J9CnRhcmdTdGF0IDwtICJjb25mLmxvdyIKbmFtZWRUaWR5Q0kgJT4lIGRwbHlyOjpzZWxlY3QoVGF4b25YLCBUYXhvblksIHRhcmdTdGF0KSAlPiUgc3ByZWFkKGtleSA9IFRheG9uWSwgdmFsdWUgPSB0YXJnU3RhdCkgJT4lCnJlbW92ZV9yb3duYW1lcyAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCJUYXhvblgiKSAlPiUKYXMuZGlzdCAlPiUgYXMubWF0cml4IC0+IHBoaWRhdGEKCnBoaV9kZCA8LSBhcy5kaXN0KHBoaWRhdGEpCnBoaV9oYyA8LSBoY2x1c3QocGhpX2RkKQoKcGhpZGF0YSAlPiUKIy5bcGhpX2hjJG9yZGVyLCBwaGlfaGMkb3JkZXJdICU+JSAjIHRoaXMgd2F5IGFsc28gd29ya2VkIGp1c3QgZmluZQpyZXNoYXBlMjo6bWVsdCgpICU+JQptdXRhdGUoVmFyMSA9IGZhY3RvcihWYXIxLCBsZXZlbHMgPSB1bmlxdWUocGhpX2hjJGxhYmVscylbcGhpX2hjJG9yZGVyXSkpICU+JQptdXRhdGUoVmFyMiA9IGZhY3RvcihWYXIyLCBsZXZlbHMgPSB1bmlxdWUocGhpX2hjJGxhYmVscylbcGhpX2hjJG9yZGVyXSkpICU+JQpwYXNzLT4gdG1wCgpnZ3Bsb3QodG1wLCBhZXMoVmFyMSwgVmFyMiwgZmlsbCA9KHZhbHVlKSkpICsgCmdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KGhpZ2ggPSAiZ3JleTkwIiwgbG93ID0gInJlZCIsIAogICAgc3BhY2UgPSAiTGFiIiwgCiAgICBuYW1lPSJwaGkiLAogICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoTkEsIDMpLCBuYS52YWx1ZSA9ICJ3aGl0ZSIpICsKIyAgdGhlbWVfbWluaW1hbCgpKyAjIG1pbmltYWwgdGhlbWUKIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KAogICAgIGFuZ2xlID0gOTAsIHZqdXN0ID0gMSwgc2l6ZSA9IDcsIGhqdXN0ID0gMSwKICAgICBmYWNlID0gaWZlbHNlKGxldmVscyh0bXAkVmFyMSkgJWluJSB1c2VGYW1pbHksICJib2xkIiwgInBsYWluIiksCiAgICAgY29sb3VyID0gaWZlbHNlKGxldmVscyh0bXAkVmFyMSkgJWluJSB1c2VGYW1pbHksICJibGFjayIsICJncmV5MzAiKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoCiAgICAgICAgICAgc2l6ZSA9IDcsCiAgICAgICAgICAgZmFjZSA9IGlmZWxzZShsZXZlbHModG1wJFZhcjEpICVpbiUgTURTMUZhbSwgImJvbGQiLCAicGxhaW4iKSwKICAgICAgICAgICBjb2xvdXIgPSBpZmVsc2UobGV2ZWxzKHRtcCRWYXIxKSAlaW4lIGxvd01EUzFGYW0sICJyZWQiLAogICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShsZXZlbHModG1wJFZhcjEpICVpbiUgaGlnaE1EUzFGYW0sICJibHVlIiwgImdyZXkzMCIpKQogICAgICAgKSkrCiBjb29yZF9maXhlZCgpICsKbGFicyh4ID0gIkZhbWlseSBBbnkgSWdnIix5ID0gIkZhbWlseSBNRFMxIChyZWQtbG93LCBibHVlLWhpZ2gpIiApLT4gcF9waGlfbG93CnBfcGhpX2xvdwoKIyBnZ3NhdmUoImZpZ3VyZXMvcGhpX3ZzX21kczFfYW5kX2lnZy5wbmciLCBwX3BoaV8xLCB3aWR0aCA9IDYsIGhlaWdodCA9IDYpCmBgYAoKYGBge3J9CnRhcmdTdGF0IDwtICJjb25mLmhpZ2giCm5hbWVkVGlkeUNJICU+JSBkcGx5cjo6c2VsZWN0KFRheG9uWCwgVGF4b25ZLCB0YXJnU3RhdCkgJT4lIHNwcmVhZChrZXkgPSBUYXhvblksIHZhbHVlID0gdGFyZ1N0YXQpICU+JQpyZW1vdmVfcm93bmFtZXMgJT4lIGNvbHVtbl90b19yb3duYW1lcygiVGF4b25YIikgJT4lCmFzLmRpc3QgJT4lIGFzLm1hdHJpeCAtPiBwaGlkYXRhCgpwaGlfZGQgPC0gYXMuZGlzdChwaGlkYXRhKQpwaGlfaGMgPC0gaGNsdXN0KHBoaV9kZCkKCnBoaWRhdGEgJT4lCiMuW3BoaV9oYyRvcmRlciwgcGhpX2hjJG9yZGVyXSAlPiUgIyB0aGlzIHdheSBhbHNvIHdvcmtlZCBqdXN0IGZpbmUKcmVzaGFwZTI6Om1lbHQoKSAlPiUKbXV0YXRlKFZhcjEgPSBmYWN0b3IoVmFyMSwgbGV2ZWxzID0gdW5pcXVlKHBoaV9oYyRsYWJlbHMpW3BoaV9oYyRvcmRlcl0pKSAlPiUKbXV0YXRlKFZhcjIgPSBmYWN0b3IoVmFyMiwgbGV2ZWxzID0gdW5pcXVlKHBoaV9oYyRsYWJlbHMpW3BoaV9oYyRvcmRlcl0pKSAlPiUKcGFzcy0+IHRtcAoKZ2dwbG90KHRtcCwgYWVzKFZhcjEsIFZhcjIsIGZpbGwgPSh2YWx1ZSkpKSArIApnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudChoaWdoID0gImdyZXk5MCIsIGxvdyA9ICJyZWQiLCAKICAgIHNwYWNlID0gIkxhYiIsIAogICAgbmFtZT0icGhpIiwKICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKE5BLCAzKSwgbmEudmFsdWUgPSAid2hpdGUiKSArCiMgIHRoZW1lX21pbmltYWwoKSsgIyBtaW5pbWFsIHRoZW1lCiB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCgKICAgICBhbmdsZSA9IDkwLCB2anVzdCA9IDEsIHNpemUgPSA3LCBoanVzdCA9IDEsCiAgICAgZmFjZSA9IGlmZWxzZShsZXZlbHModG1wJFZhcjEpICVpbiUgdXNlRmFtaWx5LCAiYm9sZCIsICJwbGFpbiIpLAogICAgIGNvbG91ciA9IGlmZWxzZShsZXZlbHModG1wJFZhcjEpICVpbiUgdXNlRmFtaWx5LCAiYmxhY2siLCAiZ3JleTMwIikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KAogICAgICAgICAgIHNpemUgPSA3LAogICAgICAgICAgIGZhY2UgPSBpZmVsc2UobGV2ZWxzKHRtcCRWYXIxKSAlaW4lIE1EUzFGYW0sICJib2xkIiwgInBsYWluIiksCiAgICAgICAgICAgY29sb3VyID0gaWZlbHNlKGxldmVscyh0bXAkVmFyMSkgJWluJSBsb3dNRFMxRmFtLCAicmVkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UobGV2ZWxzKHRtcCRWYXIxKSAlaW4lIGhpZ2hNRFMxRmFtLCAiYmx1ZSIsICJncmV5MzAiKSkKICAgICAgICkpKwogY29vcmRfZml4ZWQoKSArCmxhYnMoeCA9ICJGYW1pbHkgQW55IElnZyIseSA9ICJGYW1pbHkgTURTMSAocmVkLWxvdywgYmx1ZS1oaWdoKSIgKS0+IHBfcGhpX2hpZ2gKcF9waGlfaGlnaAojIGdnc2F2ZSgiZmlndXJlcy9waGlfdnNfbWRzMV9hbmRfaWdnLnBuZyIsIHBfcGhpXzEsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNikKYGBgCgpgYGB7cn0KY2hvcmRkYXRhICU+JQojQ2xlYW4gdXAgbGFiZWxzCm11dGF0ZShhbnRpZ2VuID0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKGFudGlnZW4sICJfIiwgIiAiKSkgJT4lCm11dGF0ZShhbnRpZ2VuID0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKGFudGlnZW4sICIgTW9udGgiLCAiIC0tIE1vbnRoIikpICU+JQptdXRhdGUoYW50aWdlbiA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChhbnRpZ2VuLCAiSWdHICIsICIiKSkgJT4lCgpnZ3Bsb3QoCiAgICBhZXMoeCA9IFRheG9uLCB5ID0gYW50aWdlbiwgYWxwaGEgPSBmYWN0b3IoaXNIaXQpLCBjb2xvciA9IGZhY3RvcihjbHJfc2lnbikpKSArCnNjYWxlX2FscGhhX2Rpc2NyZXRlKHJhbmdlID0gYygwLCAxKSkgKwpndWlkZXMoYWxwaGEgPSBGQUxTRSkgKwp0aGVtZV9taW5pbWFsKCkgKwogICAgIGNvb3JkX2ZpeGVkKHJhdGlvID0gMikgKwpzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoInJlZCIsICJibHVlIikpICsKIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KAogICAgIGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBzaXplID0gNywgaGp1c3QgPSAxLAogICAgIGZhY2UgPSBpZmVsc2UobGV2ZWxzKGNob3JkZGF0YSRUYXhvbikgJWluJSB1c2VGYW1pbHksICJib2xkIiwgInBsYWluIiksCiAgICAgY29sb3VyID0gaWZlbHNlKGxldmVscyhjaG9yZGRhdGEkVGF4b24pICVpbiUgdXNlRmFtaWx5LCAiYmxhY2siLCAiZ3JleTMwIikpLAogICAgIHBsb3QubWFyZ2luID0gdW5pdChjKDAsMywxLDMpLCAiY20iKQogICAgICkgKwojZ3VpZGVzKGNvbCA9IFRSVUUpICsKZ3VpZGVzKGNvbG9yPWd1aWRlX2xlZ2VuZCh0aXRsZT0iU2lnbiBvZiBHTE0iKSkgKwpsYWJzKHggPSAiRmFtaWx5Iix5ID0gIkFudGlnZW4gLS0gTW9udGgiICkgKwpnZW9tX3BvaW50KCkgLT4gZ3VpdGFyX2Nob3JkcwoKCgpwYXIgPC0gb3B0aW9ucygpCm9wdGlvbnMocmVwci5wbG90LndpZHRoPTEwLCByZXByLnBsb3QuaGVpZ2h0PSA1KQpndWl0YXJfY2hvcmRzCm9wdGlvbnMocGFyKQpgYGAKCmBgYHtyfQpwX3BoaV8xYSA8LSBwX3BoaV8xICsgCnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygxLCAzLCAtNS41LCA0KSwgImNtIikpCgpwYXIgPC0gb3B0aW9ucygpCm9wdGlvbnMocmVwci5wbG90LndpZHRoPTgsIHJlcHIucGxvdC5oZWlnaHQ9IDgpCgpwX3BoaV9jb3JkIDwtIGNvd3Bsb3Q6OnBsb3RfZ3JpZChwX3BoaV8xYSwgZ3VpdGFyX2Nob3JkcywgbnJvdyA9IDIsIGFsaWduID0gInYiKQoKcF9waGlfY29yZAoKI3BoaV9sZWdlbmQgPC0gY293cGxvdDo6Z2V0X2xlZ2VuZChwX3BoaV8xKQojIGNvd3Bsb3Q6OmdnZHJhdygKIyAgICAgY293cGxvdDo6cGxvdF9ncmlkKAojICAgICBjb3dwbG90OjpwbG90X2dyaWQocF9waGlfMWEsIGd1aXRhcl9jaG9yZHMsIG5jb2wgPSAxLCBhbGlnbiA9ICJ2IiksCiMgICAgICAgY293cGxvdDo6cGxvdF9ncmlkKHBoaV9sZWdlbmQsIE5VTEwsIG5jb2wgPSAxKSwKIyAgICAgICByZWxfd2lkdGhzID0gYygxMCwxKQojICAgICAgICAgKSkKCiBnZ3NhdmUoJ2ZpZ3VyZXMvcGhpX2hlYXRtYXBfd2l0aGxlZ2VuZC5wbmcnLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSAxMCkKCm9wdGlvbnMocGFyKQpgYGAKCiMgU3RhY2tlZCBiYXJzCgpgYGB7cn0KIyBNb3JlIGNvbG9yLWJsaW5kIGZyaWVuZGx5IGNvbG9yYmFsZXR0ZXMKI2h0dHA6Ly9jb2xvcmJyZXdlcjIub3JnLyN0eXBlPXF1YWxpdGF0aXZlJnNjaGVtZT1QYWlyZWQmbj0xMApjYjEwIDwtIGMoJyNhNmNlZTMnLCcjMWY3OGI0JywnI2IyZGY4YScsJyMzM2EwMmMnLCcjZmI5YTk5JywnI2UzMWExYycsJyNmZGJmNmYnLCcjZmY3ZjAwJywnI2NhYjJkNicsJyM2YTNkOWEnKQoKY2IxMiA8LSBjKCcjYTZjZWUzJywnIzFmNzhiNCcsJyNiMmRmOGEnLCcjMzNhMDJjJywnI2ZiOWE5OScsJyNlMzFhMWMnLCcjZmRiZjZmJywnI2ZmN2YwMCcsJyNjYWIyZDYnLCcjNmEzZDlhJywnI2ZmZmY5OScsJyNiMTU5MjgnKQoKIyBMZXNzIGNvbG9yLWJsaW5kIGZyaWVuZGx5LCBidXQgc3RpbGwgbmljZS4KI2h0dHBzOi8vc2FzaGF0Lm1lLzIwMTcvMDEvMTEvbGlzdC1vZi0yMC1zaW1wbGUtZGlzdGluY3QtY29sb3JzLwp0cnViMjAgPC0gYygnI2U2MTk0YicsJyMzY2I0NGInLCcjZmZlMTE5JywnIzAwODJjOCcsJyNmNTgyMzEnLCcjOTExZWI0JywnIzQ2ZjBmMCcsJyNmMDMyZTYnLCcjZDJmNTNjJywnI2ZhYmViZScsJyMwMDgwODAnLCcjZTZiZWZmJywnI2FhNmUyOCcsJyNmZmZhYzgnLCcjODAwMDAwJywnI2FhZmZjMycsJyM4MDgwMDAnLCcjZmZkOGIxJywnIzAwMDA4MCcsJyM4MDgwODAnLCcjRkZGRkZGJywnIzAwMDAwMCcpCmBgYAoKYGBge3J9Cm9wdGlvbnMocmVwci5wbG90LndpZHRoPTgsIHJlcHIucGxvdC5oZWlnaHQ9IDQpCmBgYAoKQm9va21hcmsgSGVyZS4gU3R1Y2sgZm9yIHN0cmFuZ2UgcmVhc29ucy4KYGBge3J9Cm9yZGVyZWRfcHViX2lkX2RmIDwtIHBzTjIgJT4lIHNhbXBsZV9kYXRhICU+JSBkcGx5cjo6c2VsZWN0KHB1Yl9pZCwgck1EUzEsIG5ld25hbWUsIE1EUzEpICU+JSBhcnJhbmdlKHJNRFMxKSAlPiUgbXV0YXRlKE1EUzEgPSByb3VuZChNRFMxLCAyKSwgZmlnM3RpY2sgPSBwYXN0ZShwdWJfaWQsIE1EUzEsc2VwID0gIl8iKSkKb3JkZXJlZF9wdWJfaWRfZGYKZmlnM3RpY2sgPC0gb3JkZXJlZF9wdWJfaWRfZGYgJT4lIHB1bGwoZmlnM3RpY2spCmBgYAoKYGBge3J9CnBfcGh5IDwtIHBsb3RfYmFyKHBzTjIsIHggPSAnbmV3bmFtZScsIGZpbGwgPSAnUGh5bHVtJykgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjYjEwKSAgKyB4bGFiKCIiKSArCmdndGl0bGUoIkFsbCBQaHlsYSIpKyB0aGVtZV9idygpICsKdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCwgdmp1c3QgPSAwLjUsIHNpemUgPSAxMCksCiAgICAgc3RyaXAudGV4dC55ID0gKGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkpICsgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmaWczdGljaykgKyBsYWJzKHggPSAicHViX2lkIF8gTURTMSIsIHkgPSAiUmVsYXRpdmUgQWJ1bmRhbmNlIikKcF9waHkKI2dnc2F2ZSgncGxvdHMvUGh5bGFfYnlfd3VmMS5wbmcnKQpgYGAKCmBgYHtyfQpwX2Zpcm0gPC0gIHN1YnNldF90YXhhKHBzTjIsIFBoeWx1bSA9PSAnRmlybWljdXRlcycpICU+JQpwbG90X2JhciggeCA9ICduZXduYW1lJywgZmlsbCA9ICdPcmRlcicpICsKdGhlbWVfYncoKSArCnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwsIHZqdXN0ID0gMC41LCBzaXplID0gMTApLAogICAgIHN0cmlwLnRleHQueSA9IChlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpKSArCnNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNiMTApICsgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmaWczdGljaykgKyBsYWJzKHggPSAicHViX2lkIF8gTURTMSIsIHkgPSAiUmVsYXRpdmUgQWJ1bmRhbmNlIikKcF9maXJtCiNnZ3NhdmUoJ3Bsb3RzL01vc3RGaXJtaWN1dGVzQXJlQ2xvc3RyaWRpYWxlcy5wbmcnKQpgYGAKCmBgYHtyfQpwX2Nsb3N0cmlkaWEgPC0gIHN1YnNldF90YXhhKHBzTjIsIENsYXNzID09ICdDbG9zdHJpZGlhJykgJT4lCnBsb3RfYmFyKCB4ID0gJ25ld25hbWUnLCBmaWxsID0gJ0ZhbWlseScpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY2IxMCkgICsgeGxhYigiIikgKwpnZ3RpdGxlKCJGYW1pbGllcyBvZiBPcmRlciBDbG9zdHJpZGlhbGVzIChBbGwgQ2xhc3MgQ2xvc3RyaWRpYSkiKSsgdGhlbWVfYncoKSArCnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwsIHZqdXN0ID0gMC41LCBzaXplID0gMTApLAogICAgIHN0cmlwLnRleHQueSA9IChlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpKSArIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZmlnM3RpY2spICsgbGFicyh4ID0gInB1Yl9pZCBfIE1EUzEiLCB5ID0gIlJlbGF0aXZlIEFidW5kYW5jZSIpCnBfY2xvc3RyaWRpYQpgYGAKCmBgYHtyfQojIHBfcG9ycGggPC0gIHN1YnNldF90YXhhKHBzTjIsIEZhbWlseSA9PSAnUG9ycGh5cm9tb25hZGFjZWFlJykgJT4lCiMgcGxvdF9iYXIoIHggPSAnbmV3bmFtZScsIGZpbGwgPSAnR2VudXMnKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNiMTApICMrIHRoZW1lX2J3KCkKIyBwX3BvcnBoCmBgYAoKYGBge3J9CiMgcF9iYWN0IDwtIHN1YnNldF90YXhhKHBzTjIsIFBoeWx1bSA9PSAnQmFjdGVyb2lkZXRlcycpICU+JSAjIGFsbCBjbGFzcyAoQmFjdGVyb2lkaWEpLCBvcmRlciAoQmFjdGVyb2lkYWxlcykKIyBwbG90X2Jhcih4ID0gJ25ld25hbWUnLCBmaWxsID0gJ0ZhbWlseScpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY2IxMCkgIysgdGhlbWVfYncoKQojIHAKCiMgZ2dzYXZlKCdmaWd1cmVzL0JhY3Rlcm9pZGV0ZXNfRmFtaWxpZXMucG5nJykKYGBgCgpgYGB7cn0KcF9DbG9zWEkgPC0gc3Vic2V0X3RheGEocHNOMiwgRmFtaWx5ID09ICdDbG9zdHJpZGlhbGVzX0luY2VydGFlX1NlZGlzX1hJJykgJT4lCnBsb3RfYmFyKCB4ID0gJ25ld25hbWUnLCBmaWxsID0gJ0dlbnVzJykgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjYjEwKSArIHhsYWIoIiIpICsKZ2d0aXRsZSgiR2VuZXJhIG9mIEZhbWlseSBDbG9zdHJpZGlhbGVzX0luY2VydGFlX1NlZGlzX1hJIikrIHRoZW1lX2J3KCkgKwp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsLCB2anVzdCA9IDAuNSwgc2l6ZSA9IDEwKSwKICAgICBzdHJpcC50ZXh0LnkgPSAoZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKSkgKyBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZpZzN0aWNrKSArIGxhYnMoeCA9ICJwdWJfaWQgXyBNRFMxIiwgeSA9ICJSZWxhdGl2ZSBBYnVuZGFuY2UiKQpwX0Nsb3NYSQojZ2dzYXZlKCdmaWd1cmVzL0Nsb3N0cmlkaWFsZXNfSW5jZXJ0YWVfU2VkaXNfWElfR2VudXMucG5nJykKYGBgCgpgYGB7cn0KCnBfQmFjdCA8LSBzdWJzZXRfdGF4YShwc04yLCBQaHlsdW0gPT0gJ0JhY3Rlcm9pZGV0ZXMnKSAlPiUKcGxvdF9iYXIoIHggPSAnbmV3bmFtZScsIGZpbGwgPSAnRmFtaWx5JykgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjYjEwKSArIHhsYWIoIiIpICsKZ2d0aXRsZSgiRmFtaWxpZXMgb2YgQ2xhc3MgQmFjdGVyb2lkZXRlcyAoQWxsIE9yZGVyIEJhY3Rlcm9pZGlhbGVzKSIpKyB0aGVtZV9idygpICsKdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCwgdmp1c3QgPSAwLjUsIHNpemUgPSAxMCksCiAgICAgc3RyaXAudGV4dC55ID0gKGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkpICsgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmaWczdGljaykgKyBsYWJzKHggPSAicHViX2lkIF8gTURTMSIsIHkgPSAiUmVsYXRpdmUgQWJ1bmRhbmNlIikKcF9CYWN0CiNnZ3NhdmUoJ2ZpZ3VyZXMvQmFjdGVyb2lkZXMucG5nJykKYGBgCgpgYGB7cn0Kb3B0aW9ucyhyZXByLnBsb3Qud2lkdGg9MTQsIHJlcHIucGxvdC5oZWlnaHQ9IDgpCnNiIDwtIGNvd3Bsb3Q6OnBsb3RfZ3JpZChwX3BoeSwgcF9CYWN0LHBfY2xvc3RyaWRpYSxwX0Nsb3NYSSxuY29sID0gMiwgbGFiZWxzID0gYygiQSIsICJCIiwgIkMiLCAiRCIpKQpzYgpjb3dwbG90OjpzYXZlX3Bsb3QoJ2ZpZ3VyZXMvc3RhY2tlZF9iYXJzLnBuZycsIHNiLCBiYXNlX3dpZHRoID0gMTQsIGJhc2VfaGVpZ2h0ID0gOCkKY293cGxvdDo6c2F2ZV9wbG90KCdmaWd1cmVzL3N0YWNrZWRfYmFycy5zdmcnLCBzYiwgYmFzZV93aWR0aCA9IDE0LCBiYXNlX2hlaWdodCA9IDgpCmBgYAoKIyBFeHBvcnRpbmcgT1RVIHRhYmxlcyBhbmQgVGF4YSB0YWJsZXMgYXQgZWFjaCBhZ2dsb21lcmF0aW9uIGxldmVsCgpgYGB7cn0KIyBwc0RmICU+JQojIG11dGF0ZShPVFUgPSBtYXAocHMsIH5kYXRhLmZyYW1lKG90dV90YWJsZSguKSkpKSAlPiUKIyBtdXRhdGUoVGF4ID0gbWFwKHBzLCB+ZGF0YS5mcmFtZSh0YXhfdGFibGUoLikpKSkgJT4lCiMgbXV0YXRlKE9UVUNvdW50ID0gbWFwIChwc0NvdW50LCB+ZGF0YS5mcmFtZShvdHVfdGFibGUoLikpKSkgJT4lCiMgcGFzcyAtPiBwc0RmMQpgYGAKYGBge3J9CnBzRGYgJT4lCm11dGF0ZShPVFUgPSBtYXAocHMsIH5kYXRhLmZyYW1lKG90dV90YWJsZSguKSkpKSAlPiUKbXV0YXRlKFRheCA9IG1hcChwcywgfmFzLmRhdGEuZnJhbWUoLkB0YXhfdGFibGVALkRhdGEpKSkgJT4lCm11dGF0ZShPVFVDb3VudCA9IG1hcCAocHNDb3VudCwgfmRhdGEuZnJhbWUob3R1X3RhYmxlKC4pKSkpICU+JQpwYXNzIC0+IHBzRGYxCmBgYAoKYGBge3J9CnBzRGYxICU+JQouWzE6NSxdICU+JQptdXRhdGUoVGF4ID0gbWFwKFRheCwgfmRwbHlyOjpzZWxlY3QoLiwtb2xkbmFtZTIpKSkgJT4lCnByaW50CmBgYAoKYGBge3J9CiMgaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvNTAzNDEwMTIvcmV0dXJuLXRoZS1tYXBwZWQtb2JqZWN0LWlmLWV4cHJlc3Npb24taW5zaWRlLW9mLXB1cnJycG9zc2libHktZmFpbHMvNTAzNDEyMDUjNTAzNDEyMDUKcm1fb2xkbmFtZTIgPC0gZnVuY3Rpb24oeCl7CiAgICBmID0gcG9zc2libHkoZnVuY3Rpb24oKSBkcGx5cjo6c2VsZWN0KHgsIC1vbGRuYW1lMiksIG90aGVyd2lzZSA9IHgpCiAgICAgICAgZigpCn0KYGBgCgpgYGB7cn0KcHNEZjEgJT4lCiMuWzE6NSxdICU+JQptdXRhdGUoVGF4ID0gbWFwKFRheCwgcm1fb2xkbmFtZTIpKSAlPiUKcGFzcyAtPiBwc0RmMWIKYGBgCgpgYGB7cn0KcHJpbnQocHNEZjEpCmBgYAoKYGBge3J9CiMgU2hvdyB3aGljaCBzcGVjaWVzIGxldmVsIE9UVXMgYXJlIGNvbnRhaW5lZCBpbiBlYWNoIGFnZ2xvbWVyYXRlZCBncm91cDoKcHNEZjFiICU+JQouWzE6NSxdICU+JQptdXRhdGUoVGF4SWR4ID0gbWFwKFRheCwgZnVuY3Rpb24oZGYpewogICAgZGYgJT4lCiAgICBtdXRhdGUodGFnID0gYXMuY2hhcmFjdGVyKHRhZyksIG9sZEdyb3VwcyA9IGFzLmNoYXJhY3RlcihvbGRHcm91cHMpKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QodGFnLCBvbGRHcm91cHMpICU+JQogICAgbXV0YXRlKG9sZEdyb3VwcyA9IHN0cnNwbGl0KG9sZEdyb3VwcywgIiwiKSkgJT4lCiAgICB1bm5lc3Qob2xkR3JvdXBzKQp9KSkgJT4lCmRwbHlyOjpzZWxlY3QodGF4TGV2ZWxzLCBUYXhJZHgpICU+JQp1bm5lc3QoVGF4SWR4KSAlPiUKbXV0YXRlKG9sZEdyb3VwcyA9IHRyaW13cyhvbGRHcm91cHMpKSAlPiUgIyBTb21lIG9mIHRoZXNlIGhhdmUgbGVhZGluZyBvciB0cmFpbGluZyB3aGl0ZXNwYWNlCnNwcmVhZCh0YXhMZXZlbHMsIHRhZykgJT4lCmRwbHlyOjpzZWxlY3Qob2xkR3JvdXBzLCBQaHlsdW0sIENsYXNzLCBPcmRlciwgRmFtaWx5LCBHZW51cykgJT4lCnBhc3MgLT4gdGF4R3JvdXBNYXBwaW5nCndyaXRlX2Nzdih0YXhHcm91cE1hcHBpbmcsICd0YWJsZXMvdGF4R3JvdXBNYXBwaW5nLmNzdicpCmBgYAoKYGBge3J9CiMgUHJpbnQgb3V0IGVhY2ggb3R1IHRhYmxlIChyZWxhdGl2ZSBhYnVuZGFuY2VzKS4Kd2FsazIocHNEZjFiJHRheExldmVscywgcHNEZjFiJE9UVSwgCiAgICAgIH53cml0ZS5jc3YoLnksIGZpbGUgPSBwYXN0ZTAoInRhYmxlcy9PVFUvb3R1XyIsLngsICIuY3N2IikpKQpgYGAKCmBgYHtyfQojIFByaW50IG91dCBlYWNoIG90dSB0YWJsZSAoY291bnRzKS4Kd2FsazIocHNEZjFiJHRheExldmVscywgcHNEZjFiJE9UVUNvdW50LCAKICAgICAgfndyaXRlLmNzdigueSwgZmlsZSA9IHBhc3RlMCgidGFibGVzL09UVS9vdHVDb3VudF8iLC54LCAiLmNzdiIpKSkKYGBgCgpgYGB7cn0KIyBQcmludCBvdXQgZWFjaCB0YXhvbm9teSB0YWJsZS4Kd2FsazIocHNEZjFiJHRheExldmVscywgcHNEZjFiJFRheCwgCiAgICAgIH53cml0ZV9jc3YoLnksIHBhdGggPSBwYXN0ZTAoInRhYmxlcy9UYXgvdGF4XyIsLngsICIuY3N2IikpKQpgYGAKCgoKCiMgUmVzcG9uc2UgdG8gcmV2aWV3ZXJzOiBSaWNobmVzcwoKClRoZSByZXZpZXdlciBhc2tzIGlmIGl0IGFzc29jaWF0ZXMgd2l0aCBNRFMxLiBJJ2xsIGNoZWNrIGZvciBhc3NvY2lhdGlvbnMgd2l0aCBpbW11bm9nZW5pY2l0eSBhcyB3ZWxsLgoKQ2FsY3VsYXRlIHRoZSBhbHBoYSBkaXZlcnNpdHkgdmFsdWVzIGZvciBwc04xCmBgYHtyfQpiTjEgPC0gYnJlYWthd2F5KHBzTjEpCmBgYAoKSW5pdGlhbCBsb29rIGF0IGFscGhhIGRpdmVyaXN0eSB2YWx1ZXMuCmBgYHtyfQpwbG90KGJOMSkKYGBgCkxhcmdlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzLiBBbHNvLCBwcm9iYWJseSBiZXN0IHRvIGV4YW1pbmUgaW4gbG9nIHNwYWNlIGdvaW5nIGZvcndhcmQuCgpSYW5nZSBvZiBicmVha2F3YXkgZXN0aW1hdGVzCmBgYHtyfQpzdW1tYXJ5KGJOMSkkZXN0aW1hdGUgJT4lIHJhbmdlCmBgYAoKCiMjIFJpY2huZXNzIHZzIE1EUzEKCmBgYHtyfQpidE5fTURTIDwtIGJldHRhKHN1bW1hcnkoYk4xKSRlc3RpbWF0ZSwKICAgICAgICAgICAgICBzdW1tYXJ5KGJOMSkkZXJyb3IsCiAgICAgICAgICAgICAgbWFrZV9kZXNpZ25fbWF0cml4KHBzTjEsICJNRFMxIikKKQpidE5fTURTCmBgYApOb3QgaW4gYW55IHdheSB0aGF0IGlzIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgKHAgPSAwLjI0NikKClNvbWUgcHJlLWNvbXB1dGluZwpgYGB7cn0KcmljaEVzdHMgPC0gYk4xICU+JSBtYXBfZGJsKCJlc3RpbWF0ZSIpCgpyaWNoQ0kgPC0gYk4xICU+JSBtYXBfZGYoImludGVydmFsIikgJT4lIHQgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgcmVuYW1lKHJpY2hMYiA9IFYxLCByaWNoVWIgPSBWMikgJT4lIG1lcmdlKGFzLmRhdGEuZnJhbWUocmljaEVzdHMpLCBieSA9ICJyb3cubmFtZXMiKSAlPiUgdHJhbnNmb3JtKHJvdy5uYW1lcyA9IFJvdy5uYW1lcywgcmljaEVzdCA9IHJpY2hFc3RzLCBSb3cubmFtZXMgPSBOVUxMLCByaWNoRXN0cyA9IE5VTEwpCiMgbmVlZCB0byBhZGQgbWRzMSB0byB0aGlzLCBvciBqdXN0IHRhY2sgdGhpcyBhbGwgb250byBwc04xcmljaAoKcHNOMXJpY2ggPC0gcHNOMQpzYW1wbGVfZGF0YShwc04xcmljaCkgPC0gbWVyZ2UocHNOMUBzYW1fZGF0YSwgcmljaENJLCBieSA9ICJyb3cubmFtZXMiKSAlPiUgdHJhbnNmb3JtKHJvdy5uYW1lcyA9IFJvdy5uYW1lcywgUm93Lm5hbWVzID0gTlVMTCkKYGBgCgpQbG90IHJpY2huZXNzIHZzIG1kczEKYGBge3J9CnBzTjFyaWNoICU+JSBzYW1wbGVfZGF0YSAlPiUgYXMuZGF0YS5mcmFtZSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBNRFMxLCB5ID0gcmljaEVzdCkpICsgZ2VvbV9wb2ludCgpICMrIGdlb21fZXJyb3JiYXIoKQpgYGAKCkFzIGFib3ZlIGJ1dCB3aXRoIGVycm9yIGJhcnMuCgpgYGB7cn0KcHNOMXJpY2hQIDwtIHBzTjFyaWNoICU+JSBzYW1wbGVfZGF0YSAlPiUgYXMuZGF0YS5mcmFtZSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBNRFMxLCB5ID0gcmljaEVzdCwgeW1pbiA9IHJpY2hMYiwgeW1heCA9IHJpY2hVYikpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9lcnJvcmJhcigpICsgc2NhbGVfeV9sb2cxMCgpCnBzTjFyaWNoUApgYGAKCiMjIFVuaWZyYWMgRGlzdGFuY2UgdnMgUmljaG5lc3MKVGhlIHJldmlld2VyIGFjdHVhbGx5IGFza2VkIHdoZXRoZXIgdW5pZnJhYyBkaXN0YW5jZSBhc3NvY2lhdGVzIHdpdGggYWxwaGEgZGl2ZXJzaXR5LiBJJ3ZlIGRvbmUgdGhhdCB3aXRoIE1EUzEsIGJ1dCBub3QgZGlzdGFuY2UgcGVyIHNlLgpMZXRzIGRvIG9uZSBrZXJuZWwgcmVncmVzc2lvbiwgd2hlcmUgd2UgYXNrIHdoZXRoZXIgdW5pZnJhYyBkaXN0YW5jZSBhc3NvY2lhdGVzIHdpdGggcmljaG5lc3MuCgoKCktlcm5lbCByZWdyZXNzaW9uLCB3ZWlnaHRlZCB1bmlmcmFjIHZzIHJpY2huZXNzCmBgYHtyfQptaXJSaWNoIDwtIE1pUktBVCh5ID0gcmljaEVzdHMsIEtzID0gd3VmS04yLCBvdXRfdHlwZSA9ICJDIiwgbWV0aG9kID0gJ3Blcm11dGF0aW9uJywgbnBlcm0gPSBqbnBlcm0pCm1pclJpY2gKYGBgClZlcnkgc2ltaWxhciBwLXZhbHVlIHRvIHJ1bm5pbmcgYmV0dGEgYWdhaW5zdCBNRFMxLgoKUENvQSBmaWd1cmUgdGhhdCBjb21wYXJlcyBNRFMxIGFuZCBNRFMyIHRvIHJpY2huZXNzCmBgYHtyfQpwc04xcmljaE1EU1A8LSBwc04xcmljaCAlPiUKc2FtcGxlX2RhdGEoKSAlPiUKZ2dwbG90KGFlcyh4ID0gTURTMSwgeSA9IE1EUzIpKSArIGdlb21fcG9pbnQoYWVzKGZpbGwgPSByaWNoRXN0KSwgc2l6ZSA9IDUsIHN0cm9rZSA9IDEsIHNoYXBlID0gMjEpICsKdmlyaWRpczo6c2NhbGVfZmlsbF92aXJpZGlzKG5hbWUgPSAncmljaEVzdCcsIGRpcmVjdGlvbiA9IDEsIG9wdGlvbiA9ICJ2aXJpZGlzIikgKwogIGNvb3JkX2ZpeGVkKHNxcnQocHNOMi5wY29hJENBJGVpZ1syXS9wc04yLnBjb2EkQ0EkZWlnWzFdKSkgKwojc2NhbGVfY29sb3VyX21hbnVhbChuYW1lID0gJ2dwNDEgUHJpbWFyeScsIHZhbHVlcyA9IGMoJ2JsYWNrJywgJ2dyZXk3MCcpKSArIApjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkKcHNOMXJpY2hNRFNQCmBgYAoKCiMjIyBUd28gdGV4dCBleGFtcGxlcywgd2hlcmUgd2Ugd2lsIHNlZSBpZiB3ZSBjYW4gcmVsYXRlIHJpY2huZXNzIHRvIGEgcGFyYW1ldGVyCgpIZXJlIGlzIGEgZGlzY3JldGUgZXhhbXBsZQpvcmlnaW5hbGx5IEkgdXNlZCBHcDEyMCBtb250aCA2LjUuIFN3aXRjaGluZyB0byBncDQxIG1vbnRoIDYuNSwgc2luY2Ugc3Vic2VxdWVudCBhbmFseWlzIHN1Z2dlc3RlZCB0aGF0IGl0IGRvZXMgc2hvdyBhIHJlbGF0aW9uc2hpcCwgYW5kIHNvIG1ha2VzIGEgbW9yZSB1c2VmdWwgZXhlbXBsYXIKYGBge3J9CmdwTG9jbXR4IDwtIGNiaW5kKDEsCiAgcHNOMSAlPiUgc2FtcGxlX2RhdGEgJT4lIGFzLm1hdHJpeCAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgcHVsbChJZ0dfZ3A0MV9Nb250aF82LjUpICU+JSBhcy5jaGFyYWN0ZXIgJT4lIGFzLm51bWVyaWMgJT4lIG1lZGNvZGUKKQpjb2xuYW1lcyhncExvY210eCkgPSBjKCIoSW50ZXJjZXB0KSIsICJwcmVkaWN0b3JzIikKYGBgCgpgYGB7cn0KYnROTG9jIDwtIGJldHRhKHN1bW1hcnkoYk4xKSRlc3RpbWF0ZSwKICAgICAgICAgICAgICBzdW1tYXJ5KGJOMSkkZXJyb3IsCiAgICAgICAgICAgICAgZ3BMb2NtdHgKKQpidE5Mb2MKYGBgCgojIyBDb21wYXJlIHJpY2huZXNzIHRvIGVhY2ggaW1tdW5vZ2VuaWNpdHkgcGFyYW1ldGVyLgoKU3RlcCAxLCBtYWtlIGEgZGF0YSBmcmFtZSB3aXRoIEJOMSwgYnV0IHB1Yl9pZCBmb3IgZWFjaCBzYW1wbGUKClByZSBjYWxjdWxhdGlvbnMKCmBgYHtyfQpTYW1wbGVOYW1lVG9QdWJJZCA8LSBwc04xICU+JSBzYW1wbGVfZGF0YSAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCkgJT4lIGFzLnRpYmJsZSAlPiUgZHBseXI6OnNlbGVjdChyb3duYW1lLCBwdWJfaWQpICU+JSBuYS5vbWl0KCkKYGBgCgpBIGZ1bmN0aW9uIHRoYXQgdGFrZXMgaW1tdW5vZ2VuaWNpdHkgZGF0YSwgeCAod2hpY2ggd2Ugd2lsbCBwYXNzIGluIHdpdGggdGlkeXZlcnNlIG1hcHBpbmcgZnVuY3Rpb25zKSwgYWQsIHRoZSBicmVha2F3YXkgcmljaG5lc3Mgc3VtbWFyeSwgYW5kIGEgdHJhbnNmb3JtYXRpb24sIGRlZmF1bHRzIHRvIG1lZGNvZGUyIG9mIHRoZSBpbW11bm9nZW5pY2l0eSBkYXRhLgoKYGBge3J9CmltbXVuZVZhbHBoYSA8LSBmdW5jdGlvbih4LCBhZCA9IGJOMSwgdHJhbnNmb3JtYXRpb24gPSBtZWRjb2RlMil7CiAgI3ggPC0gYXJyYW5nZSh4LCByb3duYW1lKQogIHgyIDwtIHJpZ2h0X2pvaW4oeCwgU2FtcGxlTmFtZVRvUHViSWQsIGJ5ID0gJ3B1Yl9pZCcpCiAgeDMgPC0geDJbaXMuZmluaXRlKHgyJG1hZyksXQogIAogIGFkMiA8LSBhZFtpcy5maW5pdGUoeDIkbWFnKV0KICBjbGFzcyhhZDIpIDwtIGMoImFscGhhX2VzdGltYXRlcyIsICJsaXN0IikKICAKICBncFhtdHggPSBjYmluZCgxLCB0cmFuc2Zvcm1hdGlvbih4MyRtYWcpKQogICNncFhtdHgKICAKICAgdGhpbmcgPC0gYmV0dGEoc3VtbWFyeShhZDIpJGVzdGltYXRlLAogICAgICAgICAgICAgICBzdW1tYXJ5KGFkMikkZXJyb3IsCiAgICAgICAgICAgICAgIGdwWG10eAogKQogICBvdXQgPC0gYXMuZGF0YS5mcmFtZSh0KHRoaW5nJHRhYmxlWzIsXSkpCiAgICNjb2xuYW1lcyhvdXQpID0gInB2YWwiCiAgIAogICAjIGFkZCBSXjIgdmFsdWUsIGZyb20gc2ltcGxlIHBlYXJzb24gY29ycmVsYXRpb24KICAgbG9jQ29yIDwtIGNvcihzdW1tYXJ5KGFkMikkZXN0aW1hdGUsIGdwWG10eFssMl0sIG1ldGhvZCA9ICJwZWFyc29uIikKICAgcjIgPC0gbG9jQ29yXjIKICAgb3V0MiA8LSBkYXRhLmZyYW1lKG91dCwgcjIpCiAgIAogICBvdXQyCn0KCiMgdGVzdCBhIHNpbmdsZSB1c2UgY2FzZQppbW11bmVWYWxwaGEodXNlLmltbXVuZSAlPiUgZmlsdGVyKHZpc2l0bm8gPT0gOSwgdHlwZSA9PSAiSWdHIiwgYW50aWdlbiA9PSAiQ29uLjYuZ3AxMjAuQiIpLCBiTjEsIHRyYW5zZm9ybWF0aW9uID0gbWVkY29kZSkKYGBgCgpBY3R1YWxseSBydW4gdGhlIGFuYWx5c2lzLCByYXcgdGFibGUKCmBgYHtyfQppbW11bmVBbHBoYUNvbXBpbGVkIDwtIHVzZS5pbW11bmUgJT4lIAogIGdyb3VwX2J5KHR5cGUsIGFudGlnZW4sIG1vbnRoKSAlPiUKICBkbyhpbW11bmVWYWxwaGEoLikpICU+JQogIHVuZ3JvdXAgIyBpZiB5b3UgZm9yZ2V0IHRvIHVuZ3JvdXAsIHB2YWx1ZSBjYWxjdWxhdGlvbnMgZG9uJ3Qgd29yayBjb3JyZWN0bHkKaW1tdW5lQWxwaGFUYWJsZSA8LSBpbW11bmVBbHBoYUNvbXBpbGVkICU+JSBtdXRhdGUocXZhbCA9IHAuYWRqdXN0KGBwLnZhbHVlc2AsIG1ldGhvZCA9ICJCSCIpKQppbW11bmVBbHBoYVRhYmxlCmBgYAoKUHJldHR5IHVwIHRoZSB0YWJsZSBhYm92ZSBmb3IgcHVibGljYXRpb24KCmBgYHtyfQpjb25jaXNlSW1tdW5lQWxwaGFUYWJsZSA8LSBpbW11bmVBbHBoYVRhYmxlICU+JSAKICBkcGx5cjo6c2VsZWN0KFR5cGUgPSB0eXBlLCBBbnRpZ2VuID0gYW50aWdlbiwgTW9udGggPSBtb250aCwgQ29lZj1Fc3RpbWF0ZXMsIFIyID0gcjIsIFA9YHAudmFsdWVzYCwgUSA9IHF2YWwpCnRvQWxwaGFUYWJsZSA8LSBjb25jaXNlSW1tdW5lQWxwaGFUYWJsZSAlPiUKICBtdXRhdGUoCiAgICBDb2VmID0gY2VsbF9zcGVjKGZvcm1hdChDb2VmLCBkaWdpdHMgPSAzKSwgImh0bWwiLAogICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKFAgPCAwLjA1LCBULCBGKSwKICAgICAgICAgICAgICAgICAgICAgaXRhbGljID0gaWZlbHNlKFAgPCAwLjA1ICYgQ29lZiA8IDAsIFQsIEYpLAogICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gaWZlbHNlKFAgPCAwLjA1LCBpZmVsc2UoQ29lZiA8IDAsICJsaWdodHNhbG1vbiIsICJsaWdodGJsdWUiKSwgIiIpKSwKICAgIFIyID0gY2VsbF9zcGVjKHJvdW5kKFIyLCBkaWdpdHMgPSAzKSwgImh0bWwiKSwKICAgIFAgPSBjZWxsX3NwZWMoZm9ybWF0X3JvdW5kKFAsIDMpLCAiaHRtbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKFAgPCAwLjA1LCBULCBGKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBpZmVsc2UoUCA8IDAuMDUsICd5ZWxsb3cnLCAnJykKICAgICksCiAgICBRID0gY2VsbF9zcGVjKGZvcm1hdF9yb3VuZChRLCAzKSwgImh0bWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgYm9sZCA9IGlmZWxzZShRIDwgMC4yLCBULCBGKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBpZmVsc2UoUSA8IDAuMiwgJ2xpZ2h0eWVsbG93JywgJycpCiAgICApCiAgKSAlPiUKICAgbXV0YXRlKEFudGlnZW4gPSBnc3ViKCdBTlkuRU5WLlBURUcnLCAnQW55IEVOViBQVEVHJywgQW50aWdlbikpICU+JQogIG11dGF0ZShBbnRpZ2VuID0gZ3N1YignZ3A3MF9CLkNhc2VBX1YxX1YyJywgJ2dwNzAgQi5DYXNlQSBWMS1WMicsIEFudGlnZW4pKQoKdG9BbHBoYVRhYmxlICU+JSBrYWJsZSgiaHRtbCIsIGVzY2FwZSA9IEYsIGRpZ2l0cyA9IDMsIGFsaWduID0gJ2MnKSAlPiUKICBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgImhvdmVyIiwgZnVsbF93aWR0aCA9IEYpICAlPiUKICBjb2xsYXBzZV9yb3dzKGNvbHVtbnMgPSAxOjIsIGxhdGV4X2hsaW5lID0gImZ1bGwiKSAlPiUKICBwYXNzLT4gY29uY2lzZUFscGhhVGFibGUuaHRtbAoKY29uY2lzZUFscGhhVGFibGUuaHRtbCAlPiUgY2F0KGZpbGUgPSAndGFibGVzL2NvbmNpc2VBbHBoYVRhYmxlLmh0bWwnKQoKY29uY2lzZUFscGhhVGFibGUuaHRtbApgYGAKCgoKCiMjIyBBcyBhYm92ZSBidXQgd2l0aCBjb250aW51b3VzIGltbXVub2dlbmljaXR5IGRhdGEuCgoKCmBgYHtyfQppbW11bmVBbHBoYUNvbXBpbGVkR2F1cyA8LSB1c2UuaW1tdW5lICU+JSAKICBncm91cF9ieSh0eXBlLCBhbnRpZ2VuLCBtb250aCkgJT4lCiAgZG8oaW1tdW5lVmFscGhhKC4sIHRyYW5zZm9ybWF0aW9uID0gamFjX2JveF9jb3hfMikpICU+JSAKICB1bmdyb3VwCmBgYAoKYGBge3J9CmltbXVuZUFscGhhVGFibGVHYXVzIDwtIGltbXVuZUFscGhhQ29tcGlsZWRHYXVzICU+JSBtdXRhdGUocXZhbCA9IHAuYWRqdXN0KGBwLnZhbHVlc2AsIG1ldGhvZCA9ICJCSCIpKQppbW11bmVBbHBoYVRhYmxlR2F1cwpgYGAKCmBgYHtyfQpjb25jaXNlSW1tdW5lQWxwaGFUYWJsZUdhdXMgPC0gaW1tdW5lQWxwaGFUYWJsZUdhdXMgJT4lIAogIGRwbHlyOjpzZWxlY3QoVHlwZSA9IHR5cGUsIEFudGlnZW4gPSBhbnRpZ2VuLCBNb250aCA9IG1vbnRoLCBDb2VmPUVzdGltYXRlcywgUjIgPSByMiwgUD1gcC52YWx1ZXNgLCBRID0gcXZhbCkKdG9BbHBoYVRhYmxlR2F1cyA8LSBjb25jaXNlSW1tdW5lQWxwaGFUYWJsZUdhdXMgJT4lCiAgbXV0YXRlKAogICAgQ29lZiA9IGNlbGxfc3BlYyhmb3JtYXQoQ29lZiwgZGlnaXRzID0gMyksICJodG1sIiwKICAgICAgICAgICAgICAgICAgICAgYm9sZCA9IGlmZWxzZShQIDwgMC4wNSwgVCwgRiksCiAgICAgICAgICAgICAgICAgICAgIGl0YWxpYyA9IGlmZWxzZShQIDwgMC4wNSAmIENvZWYgPCAwLCBULCBGKSwKICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IGlmZWxzZShQIDwgMC4wNSwgaWZlbHNlKENvZWYgPCAwLCAibGlnaHRzYWxtb24iLCAibGlnaHRibHVlIiksICIiKSksCiAgICBSMiA9IGNlbGxfc3BlYyhpZmVsc2UoUjIgPCAwLjAxLCBmb3JtYXQoUjIsIGRpZ2l0cyA9IDIpLHJvdW5kKFIyLCBkaWdpdHMgPSAyKSkgLCAiaHRtbCIpLAogICAgUCA9IGNlbGxfc3BlYyhmb3JtYXRfcm91bmQoUCwgMyksICJodG1sIiwKICAgICAgICAgICAgICAgICAgICAgICAgIGJvbGQgPSBpZmVsc2UoUCA8IDAuMDUsIFQsIEYpLAogICAgICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IGlmZWxzZShQIDwgMC4wNSwgJ3llbGxvdycsICcnKQogICAgKSwKICAgIFEgPSBjZWxsX3NwZWMoZm9ybWF0X3JvdW5kKFEsIDMpLCAiaHRtbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICBib2xkID0gaWZlbHNlKFEgPCAwLjIsIFQsIEYpLAogICAgICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IGlmZWxzZShRIDwgMC4yLCAnbGlnaHR5ZWxsb3cnLCAnJykKICAgICkKICApICU+JQogICBtdXRhdGUoQW50aWdlbiA9IGdzdWIoJ0FOWS5FTlYuUFRFRycsICdBbnkgRU5WIFBURUcnLCBBbnRpZ2VuKSkgJT4lCiAgbXV0YXRlKEFudGlnZW4gPSBnc3ViKCdncDcwX0IuQ2FzZUFfVjFfVjInLCAnZ3A3MCBCLkNhc2VBIFYxLVYyJywgQW50aWdlbikpCgp0b0FscGhhVGFibGVHYXVzICU+JSBrYWJsZSgiaHRtbCIsIGVzY2FwZSA9IEYsIGRpZ2l0cyA9IDMsIGFsaWduID0gJ2MnKSAlPiUKICBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgImhvdmVyIiwgZnVsbF93aWR0aCA9IEYpICAlPiUKICBjb2xsYXBzZV9yb3dzKGNvbHVtbnMgPSAxOjIsIGxhdGV4X2hsaW5lID0gImZ1bGwiKSAlPiUKICBwYXNzLT4gY29uY2lzZUFscGhhVGFibGVHYXVzLmh0bWwKCmNvbmNpc2VBbHBoYVRhYmxlR2F1cy5odG1sICU+JSBjYXQoZmlsZSA9ICd0YWJsZXMvY29uY2lzZUFscGhhVGFibGVHYXVzLmh0bWwnKQoKY29uY2lzZUFscGhhVGFibGVHYXVzLmh0bWwKYGBgCgoKClJpY2huZXNzIHZzIGFscGhhIGFib3ZlLCBidXQgdGhpcyB0aW1lIGNvbG9yY29kaW5nIGJ5IGdyb3VwLgoKYGBge3J9CnBzTjFyaWNoUF9ncDQxbTYgPC0gcHNOMXJpY2ggJT4lIHNhbXBsZV9kYXRhICU+JSBhcy5kYXRhLmZyYW1lICU+JQogIGdncGxvdChhZXMoeCA9IE1EUzEsIHkgPSByaWNoRXN0LCB5bWluID0gcmljaExiLCB5bWF4ID0gcmljaFViLCBmaWxsID0gYXMuZmFjdG9yKG1lZGNvZGVfaGwoSWdHX2dwNDFfTW9udGhfNi41KSkpKSArIGdlb21fcG9pbnQoc2hhcGUgPSAyMSwgc2l6ZSA9IDQpICsgZ2VvbV9lcnJvcmJhcigpICsgc2NhbGVfeV9sb2cxMCgpICsgc2NhbGVfZmlsbF92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hbWUgPSAnZ3A0MSBQcmltYXJ5IFRpbWVwb2ludCcpICsgbGFicyh4ID0gIk1EUzEiLCB5ID0gIlJpY2huZXNzICgjIEFTVnMpIikgKyBjb3dwbG90Ojp0aGVtZV9jb3dwbG90KCkKcHNOMXJpY2hQX2dwNDFtNgpgYGAKCgpTdW1tYXJ5IGZpZ3VyZSBmb3Igb2YgYWxwaGEKCgoKQ29tYmluZWQgZmlndXJlCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoPTh9CnBhciA8LSBvcHRpb25zKCkKI29wdGlvbnMocmVwci5wbG90LndpZHRoPTYsIHJlcHIucGxvdC5oZWlnaHQ9IDExKQojZyA8LSBncmlkLmFycmFuZ2Uod3Vmb3JkX2dwNDEsIHd1Zm9yZF9ncDEyMCwgbmNvbCA9IDIpCmcgPC0gY293cGxvdDo6cGxvdF9ncmlkKHBzTjFyaWNoTURTUCwgcHNOMXJpY2hQX2dwNDFtNiwgbmNvbCA9IDEsIGxhYmVscyA9IGMoIkEiLCAiQiIpLCBsYWJlbF9zaXplID0gMjQsIGFsaWduID0gInYiKQpnCgpjb3dwbG90OjpzYXZlX3Bsb3QoJ2ZpZ3VyZXMvcmljaG5lc3NNRFMxR3A0MS5wbmcnLCBnLCBiYXNlX3dpZHRoID0gNiwgYmFzZV9oZWlnaHQgPSA4KQpgYGAKCiMjIE5leHQgcXVlc3Rpb24uIERvIHRoZSBkb25vciBhbmQgbm9uIGRvbm9yIGdyb3VwcyBldmVyIGhhdmUgZGlmZmVyZW50IGltbXVuZSByZXNwb25zZXM/ClRoaXMgaXMgaW4gcmVzcG9uc2UgdG8gYSByZXZpZXdyIGNvbW1lbnQuClN0cmF0ZWd5LCB3cml0ZSBhIGZ1bmN0aW9uIHRvLCBmb3Igb25lIHZhcmlhYmxlIG9mIGludGVyZXN0LCBjb21wYXJlIHRoZSBncm91cHMsIGFzIEkgZGlkIGZvciB1bmlmcmFjIGRpc3RhbmNlLiBJJ2xsIHVzZSBhIHNpbXBsZSBsb2dpc3RpYyByZWdyZXNzaW9uIGhlcmUuClNpbWlsYXIgdG8gQ2FwVmFyLiBJIHdhbnQgYSB1c2UuaW1tdW5lIGRhdGEgc2V0IHRoYXQgaW5jbHVkZXMgd2hldGhlciBwZW9wbGUgYXJlIGEgZG9ub3IgYXMgYSBjb2x1bW4sIGJ1dCB0aGF0IAoKYGBge3J9CmltbXVuZS5kYXRhICU+JQptdXRhdGUoaXNEb25vciA9IHB1Yl9pZCAlaW4lIG11RG9uZXJzKSAlPiUKZmlsdGVyKAogICAgKHR5cGUgPT0gJ0lnRycgJiAKICAgIGFudGlnZW4gJWluJSBhbnRzMSAmCiAgICBtb250aCAlaW4lIGMoNi41LDEyKQogICAgKSB8CiAgICAodHlwZSAlaW4lIGMoJ0lnRycsICdJZ0EnKSAmCiAgICAgYW50aWdlbiAlaW4lIGFudHMyICYKICAgICBtb250aCAlaW4lIGMoMCw2LjUsMTIpCiAgICApIHwKICAgIHR5cGUgPT0gJ0NENCsnICYKICAgIGFudGlnZW4gPT0gJ0FOWS5FTlYuUFRFRycgJgogICAgbW9udGggJWluJSBjKDYuNSwgMTIpCiAgICAgICktPiBhbGxwYXJ0aWNpcGFudHMuaW1tdW5lCmhlYWQoYWxscGFydGljaXBhbnRzLmltbXVuZSkKYGBgCgpgYGB7cn0KYWxscGFydGljaXBhbnRzLnRlc3QgPC0gYWxscGFydGljaXBhbnRzLmltbXVuZSAlPiUgZmlsdGVyKHZpc2l0bm8gPT05LCBhbnRpZ2VuID09ICJDb24uNi5ncDEyMC5CIix0eXBlID09ICJJZ0ciKQpoZWFkKGFsbHBhcnRpY2lwYW50cy50ZXN0KQpgYGAKCmBgYHtyfQpkb25vclRlc3QgPC0gZnVuY3Rpb24oeCwgdHJhbnNmb3JtYXRpb24gPSBtZWRjb2RlMiwgZmFtaWx5ID0gJ2Jpbm9taWFsJyl7CiAgbG9jX2dsbSA8LSBnbG0odHJhbnNmb3JtYXRpb24obWFnKSB+ICBpc0Rvbm9yLCBkYXRhID0geCwgZmFtaWx5ID0gZmFtaWx5KQogIGxvY19nbG0gJT4lIGJyb29tOjp0aWR5KCkgJT4lIGZpbHRlcih0ZXJtID09ICdpc0Rvbm9yVFJVRScpICU+JSBkcGx5cjo6c2VsZWN0KC10ZXJtKQp9CmRvbm9yVGVzdChhbGxwYXJ0aWNpcGFudHMudGVzdCkKYGBgCgpEbyBvbiBldmVyeXRoaW5nCmBgYHtyfQphbGxwYXJ0aWNpcGFudHMuaW1tdW5lICU+JQogIGZpbHRlcihjdCA9PSAnVCcpICU+JQogIGdyb3VwX2J5KHR5cGUsIGFudGlnZW4sIHZpc2l0bm8pICU+JQogIGRvKGRhdGEuZnJhbWUoZG9ub3JUZXN0KC4pKSkgJT4lCiAgcGFzcy0+IERvbm9yRWZmZWN0VGFibGUKYGBgCgoKYGBge3J9CkRvbm9yRWZmZWN0VGFibGUKYGBgCgpPay4gVGhlcmUgYXBwZWFycyB0byBiZSBubyBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGluIG1hZ25pdHVkZSBncm91cCBmb3IgYW55IGNhdGVnb3J5LgoKYGBge3J9CmFsbHBhcnRpY2lwYW50cy5pbW11bmUgJT4lCiAgZmlsdGVyKGN0ID09ICdUJykgJT4lCiAgZ3JvdXBfYnkodHlwZSwgYW50aWdlbiwgdmlzaXRubykgJT4lCiAgZG8oZGF0YS5mcmFtZShkb25vclRlc3QoLiwgIHRyYW5zZm9ybWF0aW9uID0gZnVuY3Rpb24oeCl7amFjX2JveF9jb3hfMih4KX0sCiAgICAgICAgICAgICAgICAgICAgIGZhbWlseSA9ICdnYXVzc2lhbicpKSkgJT4lCiAgcGFzcy0+IERvbm9yRWZmZWN0R2F1cwpgYGAKTW9ua2V5cywgSSBndWVzcyBpdHMgZGVidWcgYWNsb2NrLgoKYGBge3J9CkRvbm9yRWZmZWN0R2F1cwpgYGAKClNhbWUgZGVhbCB3aGVuIEkgZG8gYSBub3JtYWwgbG9naXN0aWMgcmVncmVzc2lvbi4KCmBgYHtyfQpzYXZlLmltYWdlKGZpbGUgPSAid29ya3NwYWNlLlJkYXRhIikKYGBg